/*-------------------------------------------------------------------------
 *
 * subselect.c
 *	  针对子选择的规划例程。
 *
 * 本模块处理子链接和公用表表达式（CTE），但不处理子查询RTE（即，不处理
 * 子 SELECT 在 FROM 中的情况）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/plan/subselect.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/paramassign.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/subselect.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"


typedef struct convert_testexpr_context
{
	PlannerInfo *root;
	List	   *subst_nodes;	/* 替换参数的节点 */
} convert_testexpr_context;

typedef struct process_sublinks_context
{
	PlannerInfo *root;
	bool		isTopQual;
} process_sublinks_context;

typedef struct finalize_primnode_context
{
	PlannerInfo *root;
	Bitmapset  *paramids;		/* 查找到的非本地 PARAM_EXEC paramids */
} finalize_primnode_context;

typedef struct inline_cte_walker_context
{
	const char *ctename;		/* 目标 CTE 的名称和相对级别 */
	int			levelsup;
	Query	   *ctequery;		/* 替换的查询 */
} inline_cte_walker_context;


static Node *fc_build_subplan(PlannerInfo *fc_root, Plan *fc_plan, PlannerInfo *fc_subroot,
						   List *fc_plan_params,
						   SubLinkType fc_subLinkType, int fc_subLinkId,
						   Node *fc_testexpr, List *fc_testexpr_paramids,
						   bool fc_unknownEqFalse);
static List *fc_generate_subquery_params(PlannerInfo *fc_root, List *fc_tlist,
									  List **fc_paramIds);
static List *fc_generate_subquery_vars(PlannerInfo *fc_root, List *fc_tlist,
									Index fc_varno);
static Node *fc_convert_testexpr(PlannerInfo *fc_root,
							  Node *fc_testexpr,
							  List *fc_subst_nodes);
static Node *fc_convert_testexpr_mutator(Node *fc_node,
									  convert_testexpr_context *fc_context);
static bool fc_subplan_is_hashable(Plan *fc_plan);
static bool fc_subpath_is_hashable(Path *fc_path);
static bool fc_testexpr_is_hashable(Node *fc_testexpr, List *fc_param_ids);
static bool fc_test_opexpr_is_hashable(OpExpr *fc_testexpr, List *fc_param_ids);
static bool fc_hash_ok_operator(OpExpr *fc_expr);
static bool fc_contain_dml(Node *fc_node);
static bool fc_contain_dml_walker(Node *fc_node, void *fc_context);
static bool fc_contain_outer_selfref(Node *fc_node);
static bool fc_contain_outer_selfref_walker(Node *fc_node, Index *fc_depth);
static void fc_inline_cte(PlannerInfo *fc_root, CommonTableExpr *fc_cte);
static bool fc_inline_cte_walker(Node *fc_node, inline_cte_walker_context *fc_context);
static bool fc_simplify_EXISTS_query(PlannerInfo *fc_root, Query *fc_query);
static Query *fc_convert_EXISTS_to_ANY(PlannerInfo *fc_root, Query *fc_subselect,
									Node **fc_testexpr, List **fc_paramIds);
static Node *fc_replace_correlation_vars_mutator(Node *fc_node, PlannerInfo *fc_root);
static Node *fc_process_sublinks_mutator(Node *fc_node,
									  process_sublinks_context *fc_context);
static Bitmapset *fc_finalize_plan(PlannerInfo *fc_root,
								Plan *fc_plan,
								int fc_gather_param,
								Bitmapset *fc_valid_params,
								Bitmapset *fc_scan_params);
static bool fc_finalize_primnode(Node *fc_node, finalize_primnode_context *fc_context);
static bool fc_finalize_agg_primnode(Node *fc_node, finalize_primnode_context *fc_context);


/*
 * 获取计划输出第一个列的数据类型/类型修饰符/排序规则。
 *
 * 这些信息存储用于 ARRAY_SUBLINK 执行和
 * exprType()/exprTypmod()/exprCollation()，这些函数无法访问与
 * SubPlan 节点关联的计划。我们实际上只需要这些信息
 * 用于 EXPR_SUBLINK 和 ARRAY_SUBLINK 子计划，但为了
 * 一致性，总是保存这些信息。
 */
static void get_first_col_type(Plan *fc_plan, Oid *fc_coltype, int32 *fc_coltypmod,
				   Oid *fc_colcollation)
{
	/* 在 EXISTS 等情况下，结果列表可能为空；随意使用 VOID */
	if (fc_plan->targetlist)
	{
		TargetEntry *fc_tent = linitial_node(TargetEntry, fc_plan->targetlist);

		if (!fc_tent->resjunk)
		{
			*fc_coltype = exprType((Node *) fc_tent->expr);
			*fc_coltypmod = exprTypmod((Node *) fc_tent->expr);
			*fc_colcollation = exprCollation((Node *) fc_tent->expr);
			return;
		}
	}
	*fc_coltype = VOIDOID;
	*fc_coltypmod = -1;
	*fc_colcollation = InvalidOid;
}

/*
 * 将由解析器创建的 SubLink 转换为 SubPlan。
 *
 * 我们得到了 SubLink 的包含查询、类型、ID 和测试表达式。
 * 我们还被告知这个表达式是否出现在 WHERE/HAVING 资格的顶层。
 *
 * 注意：我们假设 testexpr 已经过 AND/OR 扁平化（实际上，
 * 它已经经过 eval_const_expressions），但没有转换为
 * 隐式 AND 形式；并且其中的任何 SubLinks 应该已经被
 * 转换为 SubPlans。然而，子查询尚未触及。
 *
 * 结果是我们需要替代 SubLink 节点的可执行表达式中的内容。
 * 如果我们将子计划作为常规子计划处理，这将是构造的 SubPlan 节点。
 * 如果我们将子计划作为 InitPlan，那么 SubPlan 节点将进入
 * root->init_plans，而我们在这里返回的是一个
 * 表示 InitPlan 结果的表达树：通常只是一个表示
 * 单个标量结果的 Param 节点，但可能是包含
 * 多个 Param 节点的行比较树，或者对于 MULTIEXPR 子查询，
 * 一个简单的 NULL 常量（因为真实输出的 Params 在树的其他地方，
 * 而 MULTIEXPR 子查询本身在一个 resjunk 结果列表项中，其值不重要）。
 */
static Node * fc_make_subplan(PlannerInfo *fc_root, Query *fc_orig_subquery,
			 SubLinkType fc_subLinkType, int fc_subLinkId,
			 Node *fc_testexpr, bool fc_isTopQual)
{
	Query	   *fc_subquery;
	bool		fc_simple_exists = false;
	double		fc_tuple_fraction;
	PlannerInfo *fc_subroot;
	RelOptInfo *fc_final_rel;
	Path	   *fc_best_path;
	Plan	   *fc_plan;
	List	   *fc_plan_params;
	Node	   *fc_result;

	/*
	 * 复制源查询节点。这是一个快速且脏的解决方法，以解决
	 * 解析器可以生成具有多个链接到相同子查询节点的树，
	 * 但规划器想要在查询上写入内容的事实。
	 * 尝试在我们进行查询树重设计时清理这个问题...
	 */
	fc_subquery = copyObject(fc_orig_subquery);

	/*
	 * 如果它是一个 EXISTS 子计划，我们可能能够简化它。
	 */
	if (fc_subLinkType == EXISTS_SUBLINK)
		fc_simple_exists = fc_simplify_EXISTS_query(fc_root, fc_subquery);

	/*
	 * 对于 EXISTS 子计划，告知低层规划器预期仅检索
	 * 第一元组。对于 ALL 和 ANY 子计划，如果测试条件失败或匹配，
	 * 我们将能够停止评估，因此通常不会检索所有元组；
	 * 出于没有更好主意的考虑，指定 50% 的检索。
	 * 对于 EXPR、MULTIEXPR 和 ROWCOMPARE 子计划，
	 * 使用默认行为（反正我们只期望输出一行）。
	 *
	 * 注意：如果您更改这些数字，还要更改 path/costsize.c 中的 cost_subplan()。
	 *
	 * XXX 如果 ANY 子计划是不相关的，build_subplan 可能会决定对
	 * 其输出进行哈希。在这种情况下，最好指定完全检索。
	 * 但是，目前我们只能在创建子计划后检查哈希性 :-(。
	 * （确定它是否适合哈希内存是非常困难的部分。）
	 * 因此，我们不希望对此次检索的元组百分比太过乐观，
	 * 以免选择一个不适合物化案例的计划。
	 */
	if (fc_subLinkType == EXISTS_SUBLINK)
		fc_tuple_fraction = 1.0;	/* 类似于 LIMIT 1 */
	else if (fc_subLinkType == ALL_SUBLINK ||
			 fc_subLinkType == ANY_SUBLINK)
		fc_tuple_fraction = 0.5;	/* 50% */
	else
		fc_tuple_fraction = 0.0;	/* 默认行为 */

	/* plan_params 不应该在当前查询级别中使用 */
	Assert(fc_root->plan_params == NIL);

	/* 为子查询生成路径 */
	fc_subroot = subquery_planner(fc_root->glob, fc_subquery,
							   fc_root,
							   false, fc_tuple_fraction);

	/* 隔离此特定子计划所需的参数 */
	fc_plan_params = fc_root->plan_params;
	fc_root->plan_params = NIL;

	/*
	 * 选择最佳路径并将其转化为计划。至少目前看来，
	 * 没有理由推迟进行这个。
	 */
	fc_final_rel = fetch_upper_rel(fc_subroot, UPPERREL_FINAL, NULL);
	fc_best_path = get_cheapest_fractional_path(fc_final_rel, fc_tuple_fraction);

	fc_plan = create_plan(fc_subroot, fc_best_path);

	/* 并转换为 SubPlan 或 InitPlan 格式。 */
	fc_result = fc_build_subplan(fc_root, fc_plan, fc_subroot, fc_plan_params,
						   fc_subLinkType, fc_subLinkId,
						   fc_testexpr, NIL, fc_isTopQual);

	/*
	 * 如果这是一个相关的 EXISTS，且目标列表不重要，
	 * 我们可能能够将其转换为等价的 IN，然后通过哈希实现它。
	 * 我们没有足够的信息来判断哪种方式可能更好（这取决于
	 * EXISTS 资格的预期执行次数，而我们在规划外部查询时
	 * 还太早了，无法猜测）。因此，如果可能，我们生成两个计划，
	 * 并且将其交给 setrefs.c 来决定使用哪个。
	 */
	if (fc_simple_exists && IsA(fc_result, SubPlan))
	{
		Node	   *fc_newtestexpr;
		List	   *fc_paramIds;

		/* 再次复制原始子查询 */
		fc_subquery = copyObject(fc_orig_subquery);
		/* 并重新简化 */
		fc_simple_exists = fc_simplify_EXISTS_query(fc_root, fc_subquery);
		Assert(fc_simple_exists);
		/* 查看是否可以转换为 ANY 查询 */
		fc_subquery = fc_convert_EXISTS_to_ANY(fc_root, fc_subquery,
										 &fc_newtestexpr, &fc_paramIds);
		if (fc_subquery)
		{
			/* 为 ANY 子查询生成路径；我们将需要所有行 */
			fc_subroot = subquery_planner(fc_root->glob, fc_subquery,
									   fc_root,
									   false, 0.0);

			/* 隔离此特定子计划所需的参数 */
			fc_plan_params = fc_root->plan_params;
			fc_root->plan_params = NIL;

			/* 选择最佳路径 */
			fc_final_rel = fetch_upper_rel(fc_subroot, UPPERREL_FINAL, NULL);
			fc_best_path = fc_final_rel->cheapest_total_path;

			/* 现在我们可以检查它是否可以适合哈希内存 */
			if (fc_subpath_is_hashable(fc_best_path))
			{
				SubPlan    *fc_hashplan;
				AlternativeSubPlan *fc_asplan;

				/* 好的，完成 ANY 子查询的规划 */
				fc_plan = create_plan(fc_subroot, fc_best_path);

				/* ...并转换为 SubPlan 格式 */
				fc_hashplan = castNode(SubPlan,
									fc_build_subplan(fc_root, fc_plan, fc_subroot,
												  fc_plan_params,
												  ANY_SUBLINK, 0,
												  fc_newtestexpr,
												  fc_paramIds,
												  true));
				/* 检查我们得到了预期的结果 */
				Assert(fc_hashplan->parParam == NIL);
				Assert(fc_hashplan->useHashTable);

				/* 交给 setrefs.c 来决定使用哪个计划 */
				fc_asplan = makeNode(AlternativeSubPlan);
				fc_asplan->subplans = list_make2(fc_result, fc_hashplan);
				fc_result = (Node *) fc_asplan;
				fc_root->hasAlternativeSubPlans = true;
			}
		}
	}

	return fc_result;
}

/*
 * 根据原始输入构建一个 SubPlan 节点 --- make_subplan 的子例程
 *
 * 返回 SubPlan，或者如果我们决定将其作为 InitPlan 来处理，
 * 则返回替代表达式，如 make_subplan 的注释所述。
 */
static Node * fc_build_subplan(PlannerInfo *fc_root, Plan *fc_plan, PlannerInfo *fc_subroot,
			  List *fc_plan_params,
			  SubLinkType fc_subLinkType, int fc_subLinkId,
			  Node *fc_testexpr, List *fc_testexpr_paramids,
			  bool fc_unknownEqFalse)
{
	Node	   *fc_result;
	SubPlan    *fc_splan;
	bool		fc_isInitPlan;
	ListCell   *fc_lc;

	
/*
	 * 初始化 SubPlan 节点。请注意 plan_id、plan_name 和 cost 字段
	 * 的设置在后面。
	 */
	fc_splan = makeNode(SubPlan);
	fc_splan->subLinkType = fc_subLinkType;
	fc_splan->testexpr = NULL;
	fc_splan->paramIds = NIL;
	get_first_col_type(fc_plan, &fc_splan->firstColType, &fc_splan->firstColTypmod,
					   &fc_splan->firstColCollation);
	fc_splan->useHashTable = false;
	fc_splan->unknownEqFalse = fc_unknownEqFalse;
	fc_splan->parallel_safe = fc_plan->parallel_safe;
	fc_splan->setParam = NIL;
	fc_splan->parParam = NIL;
	fc_splan->args = NIL;

	/*
	 * 创建 parParam 和 args 列表，包含当前查询级别将传递给此子计划的参数 ID 和表达式。
	 */
	foreach(fc_lc, fc_plan_params)
	{
		PlannerParamItem *fc_pitem = (PlannerParamItem *) lfirst(fc_lc);
		Node	   *fc_arg = fc_pitem->item;

		/*
		 * Var、PlaceHolderVar、Aggref 或 GroupingFunc 已经被
		 * 调整为具有正确的 varlevelsup、phlevelsup 或
		 * agglevelsup。
		 *
		 * 如果它是一个 PlaceHolderVar、Aggref 或 GroupingFunc，则其参数
		 * 可能包含尚未处理的 SubLinks（请参见 SS_replace_correlation_vars 的注释）。现在进行处理。
		 */
		if (IsA(fc_arg, PlaceHolderVar) ||
			IsA(fc_arg, Aggref) ||
			IsA(fc_arg, GroupingFunc))
			fc_arg = SS_process_sublinks(fc_root, fc_arg, false);

		fc_splan->parParam = lappend_int(fc_splan->parParam, fc_pitem->paramId);
		fc_splan->args = lappend(fc_splan->args, fc_arg);
	}

	/*
	 * 不相关或无直接相关的 EXISTS、EXPR、ARRAY、
	 * ROWCOMPARE 或 MULTIEXPR 类型的计划可以用作 initPlans。对于 EXISTS、
	 * EXPR 或 ARRAY，我们返回一个 Param，指向评估
	 * initPlan 的结果。对于 ROWCOMPARE，我们必须修改 testexpr 树以
	 * 包含 PARAM_EXEC Params，而不是解析器发出的 PARAM_SUBLINK Params，
	 * 然后返回该树。对于 MULTIEXPR，我们返回一个
	 * null 常量：包含 SubLink 的 resjunk 目标列表项
	 * 不需要返回任何有用的内容，因为引用的 Params
	 * 在其他地方。
	 */
	if (fc_splan->parParam == NIL && fc_subLinkType == EXISTS_SUBLINK)
	{
		Param	   *fc_prm;

		Assert(fc_testexpr == NULL);
		fc_prm = generate_new_exec_param(fc_root, BOOLOID, -1, InvalidOid);
		fc_splan->setParam = list_make1_int(fc_prm->paramid);
		fc_isInitPlan = true;
		fc_result = (Node *) fc_prm;
	}
	else if (fc_splan->parParam == NIL && fc_subLinkType == EXPR_SUBLINK)
	{
		TargetEntry *fc_te = linitial(fc_plan->targetlist);
		Param	   *fc_prm;

		Assert(!fc_te->resjunk);
		Assert(fc_testexpr == NULL);
		fc_prm = generate_new_exec_param(fc_root,
									  exprType((Node *) fc_te->expr),
									  exprTypmod((Node *) fc_te->expr),
									  exprCollation((Node *) fc_te->expr));
		fc_splan->setParam = list_make1_int(fc_prm->paramid);
		fc_isInitPlan = true;
		fc_result = (Node *) fc_prm;
	}
	else if (fc_splan->parParam == NIL && fc_subLinkType == ARRAY_SUBLINK)
	{
		TargetEntry *fc_te = linitial(fc_plan->targetlist);
		Oid			fc_arraytype;
		Param	   *fc_prm;

		Assert(!fc_te->resjunk);
		Assert(fc_testexpr == NULL);
		fc_arraytype = get_promoted_array_type(exprType((Node *) fc_te->expr));
		if (!OidIsValid(fc_arraytype))
			elog(ERROR, "could not find array type for datatype %s",
				 format_type_be(exprType((Node *) fc_te->expr)));
		fc_prm = generate_new_exec_param(fc_root,
									  fc_arraytype,
									  exprTypmod((Node *) fc_te->expr),
									  exprCollation((Node *) fc_te->expr));
		fc_splan->setParam = list_make1_int(fc_prm->paramid);
		fc_isInitPlan = true;
		fc_result = (Node *) fc_prm;
	}
	else if (fc_splan->parParam == NIL && fc_subLinkType == ROWCOMPARE_SUBLINK)
	{
		/* 调整 Params */
		List	   *fc_params;

		Assert(fc_testexpr != NULL);
		fc_params = fc_generate_subquery_params(fc_root,
										  fc_plan->targetlist,
										  &fc_splan->paramIds);
		fc_result = fc_convert_testexpr(fc_root,
								  fc_testexpr,
								  fc_params);
		fc_splan->setParam = list_copy(fc_splan->paramIds);
		fc_isInitPlan = true;

		/*
		 * 可执行表达式被返回以成为外部
		 * 计划的表达式树的一部分；它不会保留在 initplan 节点中。
		 */
	}
	else if (fc_subLinkType == MULTIEXPR_SUBLINK)
	{
		/*
		 * 无论它是否是 initplan，都需要为每个输出列设置一个
		 * PARAM_EXEC Param。
		 */
		List	   *fc_params;

		Assert(fc_testexpr == NULL);
		fc_params = fc_generate_subquery_params(fc_root,
										  fc_plan->targetlist,
										  &fc_splan->setParam);

		/*
		 * 将替换 Params 的列表保存在
		 * root->multiexpr_params 的第 n 个单元中；setrefs.c 将用它来替换
		 * PARAM_MULTIEXPR Params。
		 */
		while (list_length(fc_root->multiexpr_params) < fc_subLinkId)
			fc_root->multiexpr_params = lappend(fc_root->multiexpr_params, NIL);
		fc_lc = list_nth_cell(fc_root->multiexpr_params, fc_subLinkId - 1);
		Assert(lfirst(fc_lc) == NIL);
		lfirst(fc_lc) = fc_params;

		/* 如果没有 parParams，它可以是一个 initplan。 */
		if (fc_splan->parParam == NIL)
		{
			fc_isInitPlan = true;
			fc_result = (Node *) makeNullConst(RECORDOID, -1, InvalidOid);
		}
		else
		{
			fc_isInitPlan = false;
			fc_result = (Node *) fc_splan;
		}
	}
	else
	{
		/*
		 * 调整 testexpr 中的 Params，除非调用者已经处理
		 * 了它（通过传递 Param ID 列表来指示）。
		 */
		if (fc_testexpr && fc_testexpr_paramids == NIL)
		{
			List	   *fc_params;

			fc_params = fc_generate_subquery_params(fc_root,
											  fc_plan->targetlist,
											  &fc_splan->paramIds);
			fc_splan->testexpr = fc_convert_testexpr(fc_root,
											   fc_testexpr,
											   fc_params);
		}
		else
		{
			fc_splan->testexpr = fc_testexpr;
			fc_splan->paramIds = fc_testexpr_paramids;
		}

		/*
		 * 我们无法将 ALL_SUBLINK 或 ANY_SUBLINK 类型的子计划转换为
		 * initPlans，即使它们是无关或无直接相关的，因为我们需要为每个外部
		 * 元组扫描子计划的输出。但如果它是一个非直接相关的 IN (= ANY) 测试，
		 * 我们可能能够使用哈希表来避免比较所有元组。
		 */
		if (fc_subLinkType == ANY_SUBLINK &&
			fc_splan->parParam == NIL &&
			fc_subplan_is_hashable(fc_plan) &&
			fc_testexpr_is_hashable(fc_splan->testexpr, fc_splan->paramIds))
			fc_splan->useHashTable = true;

		/*
		 * 否则，我们可以选择在子计划的顶部附加一个 Material 节点，
		 * 以减少重复读取的成本。对于直接相关的子计划，这纯粹是无意义的，
		 * 因为每次都必须重新计算其结果。对于无关/无直接相关的
		 * 子计划，除非子计划的顶层计划节点会以某种方式使其输出
		 * 物化，否则我们会添加 Material。此外，如果 enable_material
		 * 为 false，那么用户不希望我们不必要地物化任何东西，
		 * 所以我们不会。
		 */
		else if (fc_splan->parParam == NIL && enable_material &&
				 !ExecMaterializesOutput(nodeTag(fc_plan)))
			fc_plan = materialize_finished_plan(fc_plan);

		fc_result = (Node *) fc_splan;
		fc_isInitPlan = false;
	}

	/*
	 * 将子计划及其 PlannerInfo 添加到全局列表。
	 */
	fc_root->glob->subplans = lappend(fc_root->glob->subplans, fc_plan);
	fc_root->glob->subroots = lappend(fc_root->glob->subroots, fc_subroot);
	fc_splan->plan_id = list_length(fc_root->glob->subplans);

	if (fc_isInitPlan)
		fc_root->init_plans = lappend(fc_root->init_plans, fc_splan);

	/*
	 * 无参数的子计划（非 initplan）应准备高效处理
	 * REWIND。如果它有直接参数，则没有意义，
	 * 因为它在每次扫描时都会重置；如果它是一个 initplan，
	 * 那么没有意义，因为它不会在没有参数更改的情况下重新运行
	 * 。哈希子计划的输入也不需要 REWIND。
	 */
	if (fc_splan->parParam == NIL && !fc_isInitPlan && !fc_splan->useHashTable)
		fc_root->glob->rewindPlanIDs = bms_add_member(fc_root->glob->rewindPlanIDs,
												   fc_splan->plan_id);

	/* 为 EXPLAIN 目的标记子计划 */
	fc_splan->plan_name = palloc(32 + 12 * list_length(fc_splan->setParam));
	sprintf(fc_splan->plan_name, "%s %d",
			fc_isInitPlan ? "InitPlan" : "SubPlan",
			fc_splan->plan_id);
	if (fc_splan->setParam)
	{
		char	   *fc_ptr = fc_splan->plan_name + strlen(fc_splan->plan_name);

		fc_ptr += sprintf(fc_ptr, " (returns ");
		foreach(fc_lc, fc_splan->setParam)
		{
			fc_ptr += sprintf(fc_ptr, "$%d%s",
						   lfirst_int(fc_lc),
						   lnext(fc_splan->setParam, fc_lc) ? "," : ")");
		}
	}

	/* 最后，填充成本估算以供后用 */
	cost_subplan(fc_root, fc_splan, fc_plan);

	return fc_result;
}

/*
 * generate_subquery_params: 构建一个 Params 列表，表示
 * sublink 的子选择的输出列，给定子选择的 targetlist。
 *
 * 我们还返回一个整型列表，包含 Params 的 paramids。
 */
static List * fc_generate_subquery_params(PlannerInfo *fc_root, List *fc_tlist, List **fc_paramIds)
{
	List	   *fc_result;
	List	   *fc_ids;
	ListCell   *fc_lc;

	fc_result = fc_ids = NIL;
	foreach(fc_lc, fc_tlist)
	{
		TargetEntry *fc_tent = (TargetEntry *) lfirst(fc_lc);
		Param	   *fc_param;

		if (fc_tent->resjunk)
			continue;

		fc_param = generate_new_exec_param(fc_root,
										exprType((Node *) fc_tent->expr),
										exprTypmod((Node *) fc_tent->expr),
										exprCollation((Node *) fc_tent->expr));
		fc_result = lappend(fc_result, fc_param);
		fc_ids = lappend_int(fc_ids, fc_param->paramid);
	}

	*fc_paramIds = fc_ids;
	return fc_result;
}

/*
 * generate_subquery_vars: 构建一个 Vars 列表，表示
 * sublink 的子选择的输出列，给定子选择的 targetlist。
 * 这些 Vars 具有指定的 varno（RTE 索引）。
 */
static List * fc_generate_subquery_vars(PlannerInfo *fc_root, List *fc_tlist, Index fc_varno)
{
	List	   *fc_result;
	ListCell   *fc_lc;

	fc_result = NIL;
	foreach(fc_lc, fc_tlist)
	{
		TargetEntry *fc_tent = (TargetEntry *) lfirst(fc_lc);
		Var		   *fc_var;

		if (fc_tent->resjunk)
			continue;

		fc_var = makeVarFromTargetEntry(fc_varno, fc_tent);
		fc_result = lappend(fc_result, fc_var);
	}

	return fc_result;
}

/*
 * convert_testexpr: 将解析器给出的 testexpr 转换为
 * 实际可执行形式。这需要将 PARAM_SUBLINK Params
 * 替换为表示子选择结果的 Params 或 Vars。
 * 要替换的节点作为 generate_subquery_params 或
 * generate_subquery_vars 的 List 结果传入。
 */
static Node * fc_convert_testexpr(PlannerInfo *fc_root,
				 Node *fc_testexpr,
				 List *fc_subst_nodes)
{
	convert_testexpr_context fc_context;

	fc_context.root = fc_root;
	fc_context.subst_nodes = fc_subst_nodes;
	return fc_convert_testexpr_mutator(fc_testexpr, &fc_context);
}

static Node * fc_convert_testexpr_mutator(Node *fc_node,
						 convert_testexpr_context *fc_context)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Param))
	{
		Param	   *fc_param = (Param *) fc_node;

		if (fc_param->paramkind == PARAM_SUBLINK)
		{
			if (fc_param->paramid <= 0 ||
				fc_param->paramid > list_length(fc_context->subst_nodes))
				elog(ERROR, "unexpected PARAM_SUBLINK ID: %d", fc_param->paramid);

			/*
			 * 我们复制列表项以避免在修改后的解析树中
			 * 具有双向链接的子结构。这在它是 Param 时可能
			 * 是不必要的，但要保持安全。
			 */
			return (Node *) copyObject(list_nth(fc_context->subst_nodes,
												fc_param->paramid - 1));
		}
	}
	if (IsA(fc_node, SubLink))
	{
		/*
		 * 如果我们遇到嵌套的 SubLink，那就没有必要也不正确地进行递归：我们可能在里面找到的任何 PARAM_SUBLINK 都属于内层 SubLink，而不是外层。因此只需按原样返回它。
		 *
		 * 这个推理依赖于假设，不会将子表达式拉入或拉出 SubLink 的 testexpr 字段，至少不会在首先替换 PARAM_SUBLINK 之后。如果我们确实想这样做，就需要完全重新考虑解析器输出表示，因为当前的 PARAM_SUBLINK 只是每个 SubLink 唯一，而不是在查询中全局唯一。将它们替换为 Vars 或 PARAM_EXEC 节点的整个目的，是为了在它们逃出 SubLink 的 testexpr 之前使其全局唯一。
		 *
		 * 注意：在 SS_process_sublinks 中调用时，这种情况不会发生，因为那会递归处理内层 SubLinks。尽管如此，在从 convert_ANY_sublink_to_join 调用时，是可能发生的。
		 */
		return fc_node;
	}
	return expression_tree_mutator(fc_node,
								   fc_convert_testexpr_mutator,
								   (void *) fc_context);
}

/*
 * subplan_is_hashable: 我们能够通过哈希实现 ANY 子计划吗？
 *
 * 这不是负责检查组合 testexpr 是否适合哈希。我们只查看子查询本身。
 */
static bool fc_subplan_is_hashable(Plan *fc_plan)
{
	double		fc_subquery_size;

	/*
	 * 子查询结果的估计大小必须适合 hash_mem。（注意：我们在这里使用堆元组开销，即使元组实际上将被存储为 MinimalTuples；这为哈希表开销提供了一些补偿因素。）
	 */
	fc_subquery_size = fc_plan->plan_rows *
		(MAXALIGN(fc_plan->plan_width) + MAXALIGN(SizeofHeapTupleHeader));
	if (fc_subquery_size > get_hash_memory_limit())
		return false;

	return true;
}

/*
 * subpath_is_hashable: 我们能够通过哈希实现 ANY 子计划吗？
 *
 * 与 subplan_is_hashable 相同，但从路径（Path）开始处理子计划。
 */
static bool fc_subpath_is_hashable(Path *fc_path)
{
	double		fc_subquery_size;

	/*
	 * 子查询结果的估计大小必须适合 hash_mem。（注意：我们在这里使用堆元组开销，即使元组实际上将被存储为 MinimalTuples；这为哈希表开销提供了一些补偿因素。）
	 */
	fc_subquery_size = fc_path->rows *
		(MAXALIGN(fc_path->pathtarget->width) + MAXALIGN(SizeofHeapTupleHeader));
	if (fc_subquery_size > get_hash_memory_limit())
		return false;

	return true;
}

/*
 * testexpr_is_hashable: ANY SubLink 的测试表达式是否可哈希？
 *
 * 要识别哈希表达式的 LHS 和 RHS，必须提供 SubLink 子查询的输出 Param ID 列表。
 */
static bool fc_testexpr_is_hashable(Node *fc_testexpr, List *fc_param_ids)
{
	/*
	 * testexpr 必须是一个单一的 OpExpr，或者是一个仅包含满足 test_opexpr_is_hashable() 的 OpExpr 的 AND 子句。
	 */
	if (fc_testexpr && IsA(fc_testexpr, OpExpr))
	{
		if (fc_test_opexpr_is_hashable((OpExpr *) fc_testexpr, fc_param_ids))
			return true;
	}
	else if (is_andclause(fc_testexpr))
	{
		ListCell   *fc_l;

		foreach(fc_l, ((BoolExpr *) fc_testexpr)->args)
		{
			Node	   *fc_andarg = (Node *) lfirst(fc_l);

			if (!IsA(fc_andarg, OpExpr))
				return false;
			if (!fc_test_opexpr_is_hashable((OpExpr *) fc_andarg, fc_param_ids))
				return false;
		}
		return true;
	}

	return false;
}

static bool fc_test_opexpr_is_hashable(OpExpr *fc_testexpr, List *fc_param_ids)
{
	/*
	 * 组合操作符必须是可哈希和严格的。对于可哈希性的需求显而易见，因为我们希望使用哈希。没有严格性，在存在 NULL 的情况下行为太不可预测。我们实际上必须假设比简单的严格性更多：对于非空输入，它也不能生成 NULL（见 nodeSubplan.c）。但是，哈希索引和哈希连接也假设这一点。
	 */
	if (!fc_hash_ok_operator(fc_testexpr))
		return false;

	/*
	 * 左侧和右侧输入必须分别属于外部查询和内部查询；因此，子查询提供的 Params 不能出现在 LHS 中，外部查询的 Vars 不能出现在 RHS 中。（通常，这一点必须成立，因为解析器构建 ANY SubLink 的 testexpr 的方式...但是函数的内联可能改变了表达式的结构，所以我们必须检查。这种情况并不常见，值得尝试优化，因此我们不担心尝试换位从句或类似的事情；我们只需确保不构建无效计划。）
	 */
	if (list_length(fc_testexpr->args) != 2)
		return false;
	if (contain_exec_param((Node *) linitial(fc_testexpr->args), fc_param_ids))
		return false;
	if (contain_var_clause((Node *) lsecond(fc_testexpr->args)))
		return false;
	return true;
}

/*
 * 检查表达式是否可哈希 + 严格
 *
 * 我们可以使用 op_hashjoinable() 和 op_strict()，但是这样做是为了避免重复的缓存查找。
 */
static bool fc_hash_ok_operator(OpExpr *fc_expr)
{
	Oid			fc_opid = fc_expr->opno;

	/* 如果不是二元操作数则快速退出 */
	if (list_length(fc_expr->args) != 2)
		return false;
	if (fc_opid == ARRAY_EQ_OP ||
		fc_opid == RECORD_EQ_OP)
	{
		/* 这些是严格的，但必须检查输入类型以确保可哈希 */
		Node	   *fc_leftarg = linitial(fc_expr->args);

		return op_hashjoinable(fc_opid, exprType(fc_leftarg));
	}
	else
	{
		/* 否则必须查找操作符属性 */
		HeapTuple	fc_tup;
		Form_pg_operator fc_optup;

		fc_tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_opid));
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "cache lookup failed for operator %u", fc_opid);
		fc_optup = (Form_pg_operator) GETSTRUCT(fc_tup);
		if (!fc_optup->oprcanhash || !func_strict(fc_optup->oprcode))
		{
			ReleaseSysCache(fc_tup);
			return false;
		}
		ReleaseSysCache(fc_tup);
		return true;
	}
}


/*
 * SS_process_ctes: 处理查询的 WITH 列表
 *
 * 考虑 WITH 列表中的每个 CTE，并选择忽略它（如果是未引用的 SELECT），将其“内联”以创建常规的子 SELECT-in-FROM，或将其转换为 initplan。
 *
 * 侧面影响是填充 root->cte_plan_ids，提供一个与 root->parse->cteList 平行的列表，并为每个 CTE 的 initplan 提供子计划 ID，或者如果没有创建 initplan，则提供一个虚拟 ID（-1）。
 */
void SS_process_ctes(PlannerInfo *fc_root)
{
	ListCell   *fc_lc;

	Assert(fc_root->cte_plan_ids == NIL);

	foreach(fc_lc, fc_root->parse->cteList)
	{
		CommonTableExpr *fc_cte = (CommonTableExpr *) lfirst(fc_lc);
		CmdType		fc_cmdType = ((Query *) fc_cte->ctequery)->commandType;
		Query	   *fc_subquery;
		PlannerInfo *fc_subroot;
		RelOptInfo *fc_final_rel;
		Path	   *fc_best_path;
		Plan	   *fc_plan;
		SubPlan    *fc_splan;
		int			fc_paramid;

		/*
		 * 忽略实际上没有被引用的 SELECT CTE。
		 */
		if (fc_cte->cterefcount == 0 && fc_cmdType == CMD_SELECT)
		{
			/* 在 cte_plan_ids 中创建一个虚拟条目 */
			fc_root->cte_plan_ids = lappend_int(fc_root->cte_plan_ids, -1);
			continue;
		}

		/*
		 * 考虑将 CTE 内联（创建 RTE_SUBQUERY RTE(s)），而不是
		 * 实现为单独计划的 CTE。
		 *
		 * 如果满足以下任一条件，我们无法内联：
		 *
		 * 1. 用户说不内联（CTEMaterializeAlways 选项）。
		 *
		 * 2. CTE 是递归的。
		 *
		 * 3. CTE 有副作用；这包括不只是一个普通的
		 * SELECT，或包含易变函数。内联可能会改变
		 * 副作用，这是不好的。
		 *
		 * 4. CTE 被多次引用，并且包含对
		 * 自身外部递归 CTE 的自我引用。内联将导致多个
		 * 递归自我引用，这是我们不支持的。
		 *
		 * 否则，我们有选择内联与否的选项。如果只有一个引用，
		 * 那应该总是有利，但如果 CTE 被多次引用，
		 * 那就不清楚了：内联会增加重复计算，但能吸收来自外部
		 * 查询级别的限制的能力可能会超过这一点。此时我们没有
		 * 足够的信息来判断是否属实，因此我们让用户表达偏好。
		 * 我们的默认行为是仅内联单次引用的 CTE，但标记为
		 * CTEMaterializeNever 的 CTE 即使被多次引用也会内联。
		 *
		 * 注意：我们最后检查易变函数，因为这比其他
		 * 需要的测试更昂贵。
		 */
		if ((fc_cte->ctematerialized == CTEMaterializeNever ||
			 (fc_cte->ctematerialized == CTEMaterializeDefault &&
			  fc_cte->cterefcount == 1)) &&
			!fc_cte->cterecursive &&
			fc_cmdType == CMD_SELECT &&
			!fc_contain_dml(fc_cte->ctequery) &&
			(fc_cte->cterefcount <= 1 ||
			 !fc_contain_outer_selfref(fc_cte->ctequery)) &&
			!contain_volatile_functions(fc_cte->ctequery))
		{
			fc_inline_cte(fc_root, fc_cte);
			/* 在 cte_plan_ids 中创建一个虚拟条目 */
			fc_root->cte_plan_ids = lappend_int(fc_root->cte_plan_ids, -1);
			continue;
		}

		/*
		 * 复制源查询节点。可能不是必要的，但我们保持
		 * 这与 make_subplan 相似。
		 */
		fc_subquery = (Query *) copyObject(fc_cte->ctequery);

		/* plan_params 不应该在当前查询级别中使用 */
		Assert(fc_root->plan_params == NIL);

		/*
		 * 为 CTE 查询生成路径。始终计划完全检索
		 * --- 我们没有足够的信息来预测其他情况。
		 */
		fc_subroot = subquery_planner(fc_root->glob, fc_subquery,
								   fc_root,
								   fc_cte->cterecursive, 0.0);

		/*
		 * 由于当前查询级别尚未包含任何 RTE，
		 * 因此不应可能 CTE 请求该级别的参数。
		 */
		if (fc_root->plan_params)
			elog(ERROR, "unexpected outer reference in CTE query");

		/*
		 * 选择最佳路径并将其转化为计划。至少目前，没有理由
		 * 推迟这样做。
		 */
		fc_final_rel = fetch_upper_rel(fc_subroot, UPPERREL_FINAL, NULL);
		fc_best_path = fc_final_rel->cheapest_total_path;

		fc_plan = create_plan(fc_subroot, fc_best_path);

		/*
		 * 为它创建一个子计划节点。这个节点与
		 * build_subplan 足够不同，因此我们无法共享代码。
		 *
		 * 请注意 plan_id、plan_name 和 cost 字段将在稍后设置。
		 */
		fc_splan = makeNode(SubPlan);
		fc_splan->subLinkType = CTE_SUBLINK;
		fc_splan->testexpr = NULL;
		fc_splan->paramIds = NIL;
		get_first_col_type(fc_plan, &fc_splan->firstColType, &fc_splan->firstColTypmod,
						   &fc_splan->firstColCollation);
		fc_splan->useHashTable = false;
		fc_splan->unknownEqFalse = false;

		/*
		 * CTE 扫描不考虑并行性（参见
		 * set_rel_consider_parallel），即使考虑的话，initPlans 也不是
		 * 线程安全的。
		 */
		fc_splan->parallel_safe = false;
		fc_splan->setParam = NIL;
		fc_splan->parParam = NIL;
		fc_splan->args = NIL;

		/*
		 * 节点不能有任何输入（因为它是一个 initplan），因此
		 * parParam 和 args 列表保持为空。 （它可以包含对
		 * 早期 CTE 输出参数 ID 的引用，但 CTE 输出不会
		 * 通过 args 列表传播。）
		 */

		/*
		 * 分配一个参数 ID 以表示 CTE 的输出。没有普通的
		 * "评估" 这个参数槽，但我们用于 setParam/chgParam 信号
		 * 的这个参数 ID，就像 CTE 计划返回一个简单的标量输出一样。
		 * （此外，执行者还滥用这个参数 ID 的 ParamExecData 槽
		 * 用于在可能扫描此 CTE 的多个 CteScan 节点之间进行通信。）
		 */
		fc_paramid = assign_special_exec_param(fc_root);
		fc_splan->setParam = list_make1_int(fc_paramid);

		/*
		 * 将子计划及其 PlannerInfo 添加到全局列表中。
		 */
		fc_root->glob->subplans = lappend(fc_root->glob->subplans, fc_plan);
		fc_root->glob->subroots = lappend(fc_root->glob->subroots, fc_subroot);
		fc_splan->plan_id = list_length(fc_root->glob->subplans);

		fc_root->init_plans = lappend(fc_root->init_plans, fc_splan);

		fc_root->cte_plan_ids = lappend_int(fc_root->cte_plan_ids, fc_splan->plan_id);

		/* 为 EXPLAIN 目的标记子计划 */
		fc_splan->plan_name = psprintf("CTE %s", fc_cte->ctename);

		/* 最后，填充成本估算以供后用 */
		cost_subplan(fc_root, fc_splan, fc_plan);
	}
}

/*
 * contain_dml: 有没有任何子查询不是纯 SELECT？
 *
 * 我们拒绝 SELECT FOR UPDATE/SHARE 以及 INSERT 等。
 */
static bool fc_contain_dml(Node *fc_node)
{
	return fc_contain_dml_walker(fc_node, NULL);
}

static bool fc_contain_dml_walker(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Query))
	{
		Query	   *fc_query = (Query *) fc_node;

		if (fc_query->commandType != CMD_SELECT ||
			fc_query->rowMarks != NIL)
			return true;

		return query_tree_walker(fc_query, fc_contain_dml_walker, fc_context, 0);
	}
	return expression_tree_walker(fc_node, fc_contain_dml_walker, fc_context);
}

/*
 * contain_outer_selfref: 是否存在外部递归自引用？
 */
static bool fc_contain_outer_selfref(Node *fc_node)
{
	Index		fc_depth = 0;

	/*
	 * 我们应该开始于一个查询，因此在检查其直接内容时
	 * 深度为 1。
	 */
	Assert(IsA(fc_node, Query));

	return fc_contain_outer_selfref_walker(fc_node, &fc_depth);
}

static bool fc_contain_outer_selfref_walker(Node *fc_node, Index *fc_depth)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, RangeTblEntry))
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) fc_node;

		/*
		 * 检查是否存在对高于我们搜索开始的查询的 CTE 的自我引用。
		 */
		if (fc_rte->rtekind == RTE_CTE &&
			fc_rte->self_reference &&
			fc_rte->ctelevelsup >= *fc_depth)
			return true;
		return false;			/* 允许 range_table_walker 继续 */
	}
	if (IsA(fc_node, Query))
	{
		/* 递归进入子查询，正确跟踪嵌套深度 */
		Query	   *fc_query = (Query *) fc_node;
		bool		fc_result;

		(*fc_depth)++;

		fc_result = query_tree_walker(fc_query, fc_contain_outer_selfref_walker,
								   (void *) fc_depth, QTW_EXAMINE_RTES_BEFORE);

		(*fc_depth)--;

		return fc_result;
	}
	return expression_tree_walker(fc_node, fc_contain_outer_selfref_walker,
								  (void *) fc_depth);
}

/*
 * inline_cte: 将给定 CTE 的 RTE_CTE 引用转换为 RTE_SUBQUERYs
 */
static void fc_inline_cte(PlannerInfo *fc_root, CommonTableExpr *fc_cte)
{
	struct inline_cte_walker_context fc_context;

	fc_context.ctename = fc_cte->ctename;
	/* 从 levelsup = -1 开始，因为我们会立即增加它 */
	fc_context.levelsup = -1;
	fc_context.ctequery = castNode(Query, fc_cte->ctequery);

	(void) fc_inline_cte_walker((Node *) fc_root->parse, &fc_context);
}

static bool fc_inline_cte_walker(Node *fc_node, inline_cte_walker_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Query))
	{
		Query	   *fc_query = (Query *) fc_node;

		fc_context->levelsup++;

		/*
		 * 在查询的 RTE 节点访问它们的内容之后访问;
		 * 否则 query_tree_walker 将下降到新内联的 CTE 查询中，
		 * 而我们不想这样。
		 */
		(void) query_tree_walker(fc_query, fc_inline_cte_walker, fc_context,
								 QTW_EXAMINE_RTES_AFTER);

		fc_context->levelsup--;

		return false;
	}
	else if (IsA(fc_node, RangeTblEntry))
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) fc_node;

		if (fc_rte->rtekind == RTE_CTE &&
			strcmp(fc_rte->ctename, fc_context->ctename) == 0 &&
			fc_rte->ctelevelsup == fc_context->levelsup)
		{
			/*
			 * 找到一个要替换的引用。生成一个 CTE 查询的副本
			 * 并适当调整外部引用的级别（例如，
			 * 针对其他 CTE）。
			 */
			Query	   *fc_newquery = copyObject(fc_context->ctequery);

			if (fc_context->levelsup > 0)
				IncrementVarSublevelsUp((Node *) fc_newquery, fc_context->levelsup, 1);

			/*
			 * 将 RTE_CTE RTE 转换为 RTE_SUBQUERY。
			 *
			 * 历史上，FOR UPDATE 子句被视为扩展到
			 * 视图和子查询，但不扩展到 CTE。我们通过不尝试
			 * 将行标记推送到新的子查询中来保留这一区别。
			 */
			fc_rte->rtekind = RTE_SUBQUERY;
			fc_rte->subquery = fc_newquery;
			fc_rte->security_barrier = false;

			/* 将 CTE 特定字段归零 */
			fc_rte->ctename = NULL;
			fc_rte->ctelevelsup = 0;
			fc_rte->self_reference = false;
			fc_rte->coltypes = NIL;
			fc_rte->coltypmods = NIL;
			fc_rte->colcollations = NIL;
		}

		return false;
	}

	return expression_tree_walker(fc_node, fc_inline_cte_walker, fc_context);
}



/*
 * convert_ANY_sublink_to_join: 尝试将 ANY SubLink 转换为连接
 *
 * 调用者在查询的某个 qual 子句的顶层发现了 ANY SubLink，但尚未检查该 SubLink 的其他属性。
 * 决定以连接方式处理此 SubLink 是否合适。
 * 如果合适，则形成一个 JoinExpr 并返回。如果 SubLink 无法
 * 转换为连接，则返回 NULL。
 *
 * 唯一不明显的输入参数是 available_rels：这是可以在
 * sublink 表达式中安全引用的查询关系集合。
 * （我们必须限制这个，以避免在外部连接的 ON 子句中出现 sublink 时改变语义。）如果
 * 转换后的 qual 引用任何父查询的 relids 之外的内容，则转换必须失败。
 *
 * 成功时，返回的 JoinExpr 的 larg = NULL，rarg = 表示已提升子查询的 jointree
 * 项。调用者必须设置 larg 以表示新连接的左侧关系，并将
 * JoinExpr 插入到上层查询的 jointree 中适当的位置
 * （通常是在左侧关系之前）。注意，传入的 SubLink 也必须从其原始位置
 * 在查询 quals 中被移除，因为返回的 JoinExpr 的 quals 替换了它。
 * （概念上，我们用常量 TRUE 替换 SubLink，然后从 qual 中省略
 * 多余的常量。）
 *
 * 成功时，调用者还需负责递归地对返回的 JoinExpr 的 rarg 和 quals 应用
 * pull_up_sublinks 处理。
 * （失败时，无需采取任何行动，因为在递归计划子选择时将应用
 * pull_up_sublinks。） 
 *
 * 成功转换的副作用包括将 SubLink 的
 * 子选择添加到查询的 rangetable，以便可以在
 * JoinExpr 的 rarg 中引用它。
 */
JoinExpr *
convert_ANY_sublink_to_join(PlannerInfo *fc_root, SubLink *fc_sublink,
							Relids fc_available_rels)
{
	JoinExpr   *fc_result;
	Query	   *fc_parse = fc_root->parse;
	Query	   *fc_subselect = (Query *) fc_sublink->subselect;
	Relids		fc_upper_varnos;
	int			fc_rtindex;
	ParseNamespaceItem *fc_nsitem;
	RangeTblEntry *fc_rte;
	RangeTblRef *fc_rtr;
	List	   *fc_subquery_vars;
	Node	   *fc_quals;
	ParseState *fc_pstate;

	Assert(fc_sublink->subLinkType == ANY_SUBLINK);

	/*
	 * 子选择不得引用任何父查询的 Vars。（较高层级的 Vars 应该是可以的。）
	 */
	if (contain_vars_of_level((Node *) fc_subselect, 1))
		return NULL;

	/*
	 * 测试表达式必须包含一些父查询的 Vars，否则它不会成为连接。
	 * （注意，它不会拥有引用子查询的 Vars，而是 Params。）
	 */
	fc_upper_varnos = pull_varnos(fc_root, fc_sublink->testexpr);
	if (bms_is_empty(fc_upper_varnos))
		return NULL;

	/*
	 * 然而，它不能引用 available_rels 之外的任何内容。
	 */
	if (!bms_is_subset(fc_upper_varnos, fc_available_rels))
		return NULL;

	/*
	 * 组合运算符和左侧表达式不得是波动的。
	 */
	if (contain_volatile_functions(fc_sublink->testexpr))
		return NULL;

	/* 为 addRangeTableEntryForSubquery 创建一个虚拟的 ParseState */
	fc_pstate = make_parsestate(NULL);

	/*
	 * 好的，将子选择提到上层范围表中。
	 *
	 * 我们在这里依赖于假设，外查询与内查询没有引用（必须为真，除了我们下面构建的 Vars）。因此，这比 pull_up_subqueries 所需的工作要简单得多。
	 */
	fc_nsitem = addRangeTableEntryForSubquery(fc_pstate,
										   fc_subselect,
										   makeAlias("ANY_subquery", NIL),
										   false,
										   false);
	fc_rte = fc_nsitem->p_rte;
	fc_parse->rtable = lappend(fc_parse->rtable, fc_rte);
	fc_rtindex = list_length(fc_parse->rtable);

	/*
	 * 为提取的子选择形成一个 RangeTblRef。
	 */
	fc_rtr = makeNode(RangeTblRef);
	fc_rtr->rtindex = fc_rtindex;

	/*
	 * 构建一个表示子选择输出的 Vars 列表。
	 */
	fc_subquery_vars = fc_generate_subquery_vars(fc_root,
										   fc_subselect->targetList,
										   fc_rtindex);

	/*
	 * 构建新连接的 qual 表达式，用这些 Vars 替换 Params。
	 */
	fc_quals = fc_convert_testexpr(fc_root, fc_sublink->testexpr, fc_subquery_vars);

	/*
	 * 最后，构建 JoinExpr 节点。
	 */
	fc_result = makeNode(JoinExpr);
	fc_result->jointype = JOIN_SEMI;
	fc_result->isNatural = false;
	fc_result->larg = NULL;		/* 调用者必须填写此内容 */
	fc_result->rarg = (Node *) fc_rtr;
	fc_result->usingClause = NIL;
	fc_result->join_using_alias = NULL;
	fc_result->quals = fc_quals;
	fc_result->alias = NULL;
	fc_result->rtindex = 0;		/* 我们不需要为其提供 RTE */

	return fc_result;
}

/*
 * convert_EXISTS_sublink_to_join：尝试将EXISTS子链接转换为连接
 *
 * 此函数的API与convert_ANY_sublink_to_join相同，
 * 只是我们还支持调用方发现NOT EXISTS的情况，
 * 所以我们需要一个额外的输入参数“under_not”。
 */
JoinExpr *
convert_EXISTS_sublink_to_join(PlannerInfo *fc_root, SubLink *fc_sublink,
							   bool fc_under_not, Relids fc_available_rels)
{
	JoinExpr   *fc_result;
	Query	   *fc_parse = fc_root->parse;
	Query	   *fc_subselect = (Query *) fc_sublink->subselect;
	Node	   *fc_whereClause;
	int			fc_rtoffset;
	int			fc_varno;
	Relids		fc_clause_varnos;
	Relids		fc_upper_varnos;

	Assert(fc_sublink->subLinkType == EXISTS_SUBLINK);

	/*
	 * 如果包含WITH，则不能平展。 (我们可以安排将
	 * WITH提取到父查询的cteList中，但这有改变
	 * 语义的风险，因为WITH应该在每个相关查询调用中执行一次。)
	 * 请注意convert_ANY_sublink_to_join不必拒绝
	 * 这种情况，因为它只是生成一个不必被平展到
	 * 父查询中的子查询RTE。
	 */
	if (fc_subselect->cteList)
		return NULL;

	/*
	 * 复制子查询，以便我们可以安全地修改它（参见
	 * make_subplan中的注释）。
	 */
	fc_subselect = copyObject(fc_subselect);

	/*
	 * 根据它在EXISTS()中使用的知识，查看子查询是否可以简化。
	 * 如果我们无法去除它的目标列表，我们必须失败，因为提取操作
	 * 使我们没有地方来评估目标列表。
	 */
	if (!fc_simplify_EXISTS_query(fc_root, fc_subselect))
		return NULL;

	/*
	 * 分离出WHERE子句。 （我们理论上也可以移除
	 * 顶层普通JOIN/ON子句，但这可能不值得麻烦。）
	 */
	fc_whereClause = fc_subselect->jointree->quals;
	fc_subselect->jointree->quals = NULL;

	/*
	 * 子选择的其余部分不得引用父查询的任何Vars。
	 * （更高层的Vars应该没问题。）
	 */
	if (contain_vars_of_level((Node *) fc_subselect, 1))
		return NULL;

	/*
	 * 另一方面，WHERE子句必须包含一些父查询的Vars，
	 * 否则它就不会成为一个连接。
	 */
	if (!contain_vars_of_level(fc_whereClause, 1))
		return NULL;

	/*
	 * 如果WHERE子句是可变的，我们也不冒优化的风险。
	 */
	if (contain_volatile_functions(fc_whereClause))
		return NULL;

	/*
	 * 子查询必须有一个非空的jointree，但我们可以做到这一点。
	 */
	replace_empty_jointree(fc_subselect);

	/*
	 * 准备将子查询提升到顶部范围表中。
	 *
	 * 我们在这里依赖于外部查询没有对内部的引用的假设
	 * （这是必然成立的）。因此这比
	 * pull_up_subqueries 要简单得多。
	 *
	 * 实际上，这甚至比 convert_ANY_sublink_to_join
	 * 要更简单。simplify_EXISTS_query 的运算确保子查询中没有
	 * 有趣的内容，除了 rtable 和 jointree，甚至 jointree 的
	 * FromExpr 也不再包含 quals。因此我们可以将 rtable
	 * 附加到我们自己的，并在我们的 jointree 中使用 FromExpr。
	 * 但首先，需要调整子查询中所有零级变量的 varnos，以
	 * 考虑到 rtable 的合并。
	 */
	fc_rtoffset = list_length(fc_parse->rtable);
	OffsetVarNodes((Node *) fc_subselect, fc_rtoffset, 0);
	OffsetVarNodes(fc_whereClause, fc_rtoffset, 0);

	/*
	 * 子查询中的上层变量现在会比之前离其父级更近一层；
	 * 特别是，任何曾经是一级的变量现在变成零级。
	 */
	IncrementVarSublevelsUp((Node *) fc_subselect, -1, 1);
	IncrementVarSublevelsUp(fc_whereClause, -1, 1);

	/*
	 * 现在 WHERE 子句已调整为匹配父查询
	 * 环境，我们可以轻松识别它所使用的所有零级关系。
	 * 小于等于 rtoffset 的属于上层查询；大于 rtoffset 的则不属于。
	 */
	fc_clause_varnos = pull_varnos(fc_root, fc_whereClause);
	fc_upper_varnos = NULL;
	while ((fc_varno = bms_first_member(fc_clause_varnos)) >= 0)
	{
		if (fc_varno <= fc_rtoffset)
			fc_upper_varnos = bms_add_member(fc_upper_varnos, fc_varno);
	}
	bms_free(fc_clause_varnos);
	Assert(!bms_is_empty(fc_upper_varnos));

	/*
	 * 现在我们已经获得了上层 varnos 的集合，可以进行最后的
	 * 检查：只能引用 available_rels。
	 */
	if (!bms_is_subset(fc_upper_varnos, fc_available_rels))
		return NULL;

	/* 现在我们可以将修改后的子查询 rtable 附加到父级 */
	fc_parse->rtable = list_concat(fc_parse->rtable, fc_subselect->rtable);

	/*
	 * 最后，构建 JoinExpr 节点。
	 */
	fc_result = makeNode(JoinExpr);
	fc_result->jointype = fc_under_not ? JOIN_ANTI : JOIN_SEMI;
	fc_result->isNatural = false;
	fc_result->larg = NULL;		/* 调用者必须填写此内容 */
	/* 如果 FromExpr 节点没有用处，则将其展平 */
	if (list_length(fc_subselect->jointree->fromlist) == 1)
		fc_result->rarg = (Node *) linitial(fc_subselect->jointree->fromlist);
	else
		fc_result->rarg = (Node *) fc_subselect->jointree;
	fc_result->usingClause = NIL;
	fc_result->join_using_alias = NULL;
	fc_result->quals = fc_whereClause;
	fc_result->alias = NULL;
	fc_result->rtindex = 0;		/* 我们不需要为其提供 RTE */

	return fc_result;
}

/*
 * simplify_EXISTS_query：移除 EXISTS 的子查询中的任何无用内容
 *
 * EXISTS 查询的重要性在于它是否返回零行或超出零行。
 * 因此，我们可以移除某些不会影响这一点的 SQL 特性。
 * 在典型用法中真正可能需要关注的部分是简化目标列表：
 * 习惯于写 "SELECT * FROM" 即使没有必要评估任何列。
 *
 * 注意：通过抑制目标列表，我们可能会导致可观察的行为
 * 变化，即评估目标列表时可能发生的任何错误不会发生，
 * 也不会有易失性函数的其他副作用。这在实践中似乎
 * 不太可能困扰任何人。
 *
 * 如果能够丢弃目标列表，则返回 true，否则返回 false。
 */
static bool fc_simplify_EXISTS_query(PlannerInfo *fc_root, Query *fc_query)
{
	/*
	 * 如果查询使用集合操作、聚合、分组集、SRF、修改 CTE、
	 * HAVING、OFFSET 或 FOR UPDATE/SHARE，则我们不会尝试
	 * 进行简化；在正常使用中似乎没有这些内容，并且它们
	 * 可能产生的影响是复杂的。（注意：我们可以忽略 "OFFSET 0"
	 * 子句，但传统上这被用作优化边界，因此我们不会进行忽略。）
	 */
	if (fc_query->commandType != CMD_SELECT ||
		fc_query->setOperations ||
		fc_query->hasAggs ||
		fc_query->groupingSets ||
		fc_query->hasWindowFuncs ||
		fc_query->hasTargetSRFs ||
		fc_query->hasModifyingCTE ||
		fc_query->havingQual ||
		fc_query->limitOffset ||
		fc_query->rowMarks)
		return false;

	/*
	 * 具有常量正值（或 NULL）的 LIMIT 不会影响 EXISTS 的
	 * 语义，因此我们可以忽略这些子句。这是值得的，因为
	 * 习惯于某些其他 DBMS 的人可能习惯于将 EXISTS(SELECT ...
	 * LIMIT 1) 作为一种优化。然而，如果 LIMIT 的其他内容
	 * 作为参数，则我们无法简化。
	 */
	if (fc_query->limitCount)
	{
		/*
		 * LIMIT 子句尚未经过 eval_const_expressions，
		 * 因此我们必须在这里应用它。看起来似乎浪费周期，
		 * 因为唯一值得担心的情况是 "LIMIT 1" ... 但是我们
		 * 实际上会看到 "LIMIT int8(1::int4)"，因此我们必须折叠
		 * 常量，否则无法识别。
		 */
		Node	   *fc_node = eval_const_expressions(fc_root, fc_query->limitCount);
		Const	   *fc_limit;

		/* 如果我们简化了子句，不如更新查询。 */
		fc_query->limitCount = fc_node;

		if (!IsA(fc_node, Const))
			return false;

		fc_limit = (Const *) fc_node;
		Assert(fc_limit->consttype == INT8OID);
		if (!fc_limit->constisnull && DatumGetInt64(fc_limit->constvalue) <= 0)
			return false;

		/* 无论目标列表是否安全，我们都可以丢弃 LIMIT。 */
		fc_query->limitCount = NULL;
	}

	/*
	 * 否则，我们可以丢弃目标列表，以及任何 GROUP、
	 * WINDOW、DISTINCT 和 ORDER BY 子句；这些子句都不会
	 * 将非零行结果更改为零行，反之亦然。（此外，由于
	 * 我们的解析树对这些子句的表示依赖于目标列表，如果
	 * 我们丢弃目标列表，最好将它们丢掉。）
	 */
	fc_query->targetList = NIL;
	fc_query->groupClause = NIL;
	fc_query->windowClause = NIL;
	fc_query->distinctClause = NIL;
	fc_query->sortClause = NIL;
	fc_query->hasDistinctOn = false;

	return true;
}

/*
 * convert_EXISTS_to_ANY：尝试将 EXISTS 转换为可哈希的
 * ANY 子链接
 *
 * 子查询预计是一个新副本，我们可以对其进行处理，
 * 并且已成功通过 simplify_EXISTS_query。
 *
 * 成功时，返回修改后的子查询，并在 *testexpr 处存储
 * 一个合适的上层测试表达式，以及在 *paramIds 处存储
 * 子查询输出的参数列表。（测试表达式已经被参数化，
 * 因此不需要通过 convert_testexpr，这也是我们必须
 * 区别处理参数 ID 的原因。）
 *
 * 失败时，返回 NULL。
 */
static Query * fc_convert_EXISTS_to_ANY(PlannerInfo *fc_root, Query *fc_subselect,
					  Node **fc_testexpr, List **fc_paramIds)
{
	Node	   *fc_whereClause;
	List	   *fc_leftargs,
			   *fc_rightargs,
			   *fc_opids,
			   *fc_opcollations,
			   *fc_newWhere,
			   *fc_tlist,
			   *fc_testlist,
			   *fc_paramids;
	ListCell   *fc_lc,
			   *fc_rc,
			   *fc_oc,
			   *fc_cc;
	AttrNumber	fc_resno;

	/*
	 * 查询不能要求目标列表，因为我们必须插入一个新的。
	 * 调用者应该已经处理过这种情况。
	 */
	Assert(fc_subselect->targetList == NIL);

	/*
	 * 分离出WHERE子句。 （我们理论上也可以移除
	 * 顶层普通JOIN/ON子句，但这可能不值得麻烦。）
	 */
	fc_whereClause = fc_subselect->jointree->quals;
	fc_subselect->jointree->quals = NULL;

	
/*
	 * 剩余的子查询不应引用任何父查询的变量。
	 * （更高层次的变量应该可以。）
	 *
	 * 注意：我们不需要单独检查聚合引用，因为我们知道
	 * 子查询尚未优化；任何更高级别的聚合引用必须因此
	 * 包含一个更高级别的变量引用。以下情况不是这样......
	 */
	if (contain_vars_of_level((Node *) fc_subselect, 1))
		return NULL;

	/*
	 * 如果WHERE子句是可变的，我们也不冒优化的风险。
	 */
	if (contain_volatile_functions(fc_whereClause))
		return NULL;

	/*
	 * 通过对 WHERE 子句进行常量简化等来清理它。
	 * 除了简化我们即将进行的处理之外，这对于能够将 
	 * WHERE 子句的某些部分向上提取到父查询中是重要的。
	 * 由于我们在父查询的 preprocess_expression() 工作中途被调用，
	 * 所以 preprocess_expression() 的早期步骤不会应用于提取的内容，
	 * 除非我们在这里执行它。对于重新放回子查询中的
	 * WHERE 子句的部分，这部分工作是部分重复的，但这不应该有害。
	 *
	 * 注意：我们不运行 flatten_join_alias_vars。这没问题， 
	 * 因为任何父别名已经被展平，并且我们不会将任何子变量（无论哪种描述）
	 * 提取到父查询中。
	 *
	 * 注意：将父查询的根传递给 eval_const_expressions 在技术上是错误的，但
	 * 我们可以逃过这一关，因为仅有的绑定参数（如果有的话）会被使用，
	 * 而这些在子根中是相同的。
	 */
	fc_whereClause = eval_const_expressions(fc_root, fc_whereClause);
	fc_whereClause = (Node *) canonicalize_qual((Expr *) fc_whereClause, false);
	fc_whereClause = (Node *) make_ands_implicit((Expr *) fc_whereClause);

	/*
	 * 我们现在有一个展平的隐式 AND 条件列表，尝试将其
	 * 拆分为“外部变量 = 内部变量”哈希条件。任何不能拆分的内容
	 * 都将返回到 newWhere 列表中。注意，我们尚未努力
	 * 确保每一侧只有外部或内部；如果我们到达结尾时会检查这一点。
	 */
	fc_leftargs = fc_rightargs = fc_opids = fc_opcollations = fc_newWhere = NIL;
	foreach(fc_lc, (List *) fc_whereClause)
	{
		OpExpr	   *fc_expr = (OpExpr *) lfirst(fc_lc);

		if (IsA(fc_expr, OpExpr) &&
			fc_hash_ok_operator(fc_expr))
		{
			Node	   *fc_leftarg = (Node *) linitial(fc_expr->args);
			Node	   *fc_rightarg = (Node *) lsecond(fc_expr->args);

			if (contain_vars_of_level(fc_leftarg, 1))
			{
				fc_leftargs = lappend(fc_leftargs, fc_leftarg);
				fc_rightargs = lappend(fc_rightargs, fc_rightarg);
				fc_opids = lappend_oid(fc_opids, fc_expr->opno);
				fc_opcollations = lappend_oid(fc_opcollations, fc_expr->inputcollid);
				continue;
			}
			if (contain_vars_of_level(fc_rightarg, 1))
			{
				/*
				 * 我们必须重新排列条件，以便将外部变量放在
				 * 左侧，因为 nodeSubplan.c 中的哈希代码期望这样。
				 * 这不应该失败，因为可哈希运算符应该有可交换性，
				 * 但要保持警惕。
				 */
				fc_expr->opno = get_commutator(fc_expr->opno);
				if (OidIsValid(fc_expr->opno) && fc_hash_ok_operator(fc_expr))
				{
					fc_leftargs = lappend(fc_leftargs, fc_rightarg);
					fc_rightargs = lappend(fc_rightargs, fc_leftarg);
					fc_opids = lappend_oid(fc_opids, fc_expr->opno);
					fc_opcollations = lappend_oid(fc_opcollations, fc_expr->inputcollid);
					continue;
				}
				/* 如果没有可交换性，就没有机会优化 WHERE 子句 */
				return NULL;
			}
		}
		/* 无法将其处理为哈希条件 */
		fc_newWhere = lappend(fc_newWhere, fc_expr);
	}

	/*
	 * 如果我们没有找到可以转换的东西，则失败。
	 */
	if (fc_leftargs == NIL)
		return NULL;

	/*
	 * 在我们打算放回子查询的内容中，不应包含任何父变量或聚合。
	 * 注意：你可能会认为我们不需要单独检查聚合，因为
	 * 更高级别的聚合必须在其参数中包含一个更高级别的变量。
	 * 但可能更高级别的变量已经被 eval_const_expressions 优化掉了。
	 * 考虑
	 *
	 * SUM(CASE WHEN false THEN uplevelvar ELSE 0 END)
	 */
	if (contain_vars_of_level((Node *) fc_newWhere, 1) ||
		contain_vars_of_level((Node *) fc_rightargs, 1))
		return NULL;
	if (fc_root->parse->hasAggs &&
		(contain_aggs_of_level((Node *) fc_newWhere, 1) ||
		 contain_aggs_of_level((Node *) fc_rightargs, 1)))
		return NULL;

	/*
	 * 并且我们打算提取的内容中不能有任何子变量。
	 * （注意：我们也需要检查子聚合，除非我们知道子查询完全
	 * 不包含聚合，因为 simplify_EXISTS_query 的检查。窗口函数也是同样的处理。）
	 */
	if (contain_vars_of_level((Node *) fc_leftargs, 0))
		return NULL;

	/*
	 * 还要拒绝我们打算提取的内容中的子链接。
	 * （可能支持这个，但似乎没什么价值。）
	 */
	if (contain_subplans((Node *) fc_leftargs))
		return NULL;

	/*
	 * 好的，调整我们正在提取的内容中的 sublevelsup。
	 */
	IncrementVarSublevelsUp((Node *) fc_leftargs, -1, 1);

	/*
	 * 放回任何仅限于子级的 WHERE 条件。
	 */
	if (fc_newWhere)
		fc_subselect->jointree->quals = (Node *) make_ands_explicit(fc_newWhere);

	/*
	 * 为子查询构建一个新的目标列表，以生成我们所需的表达式。
	 * 同时，为父查询构建一个使用参数引用子查询输出的 testexpr。
	 * （由于我们在这里直接生成参数，因此在 build_subplan 中
	 * 不需要转换 testexpr。）
	 */
	fc_tlist = fc_testlist = fc_paramids = NIL;
	fc_resno = 1;
	forfour(fc_lc, fc_leftargs, fc_rc, fc_rightargs, fc_oc, fc_opids, fc_cc, fc_opcollations)
	{
		Node	   *fc_leftarg = (Node *) lfirst(fc_lc);
		Node	   *fc_rightarg = (Node *) lfirst(fc_rc);
		Oid			fc_opid = lfirst_oid(fc_oc);
		Oid			fc_opcollation = lfirst_oid(fc_cc);
		Param	   *fc_param;

		fc_param = generate_new_exec_param(fc_root,
										exprType(fc_rightarg),
										exprTypmod(fc_rightarg),
										exprCollation(fc_rightarg));
		fc_tlist = lappend(fc_tlist,
						makeTargetEntry((Expr *) fc_rightarg,
										fc_resno++,
										NULL,
										false));
		fc_testlist = lappend(fc_testlist,
						   make_opclause(fc_opid, BOOLOID, false,
										 (Expr *) fc_leftarg, (Expr *) fc_param,
										 InvalidOid, fc_opcollation));
		fc_paramids = lappend_int(fc_paramids, fc_param->paramid);
	}

	/* 将一切放到应放置的位置，我们就完成了 */
	fc_subselect->targetList = fc_tlist;
	*fc_testexpr = (Node *) make_ands_explicit(fc_testlist);
	*fc_paramIds = fc_paramids;

	return fc_subselect;
}


/*
 * 用参数替换相关变量（上层变量）。
 *
 * 上层的占位符变量和聚合函数也会被替换。
 *
 * 注意：在 SS_process_sublinks 之后立即运行这一段是至关重要的。
 * 由于我们不递归到上层 PHV 和聚合的参数中，它们将在父查询的适当子计划参数列表中被复制，并且上层变量不会被参数替换，而只是调整了级别
 * （参见 replace_outer_placeholdervar 和 replace_outer_agg）。这正是我们希望上层变量的行为——但如果一个 PHV 或聚合的参数包含任何更上层的变量，它们必须依次被参数替换。 
 * 这将在父级运行 SS_replace_correlation_vars 时发生。因此，必须在扩展子链接到子计划之后进行。我们不希望在中间有任何步骤，否则这些步骤不会应用于上层或下层的参数表达式。
 *
 * 此外，这里的另一个相当棘手的事情是处理上层 PHV/聚合参数中的子链接。它们在中间查询级别中也不会被触及。相反，SS_process_sublinks 在将 PHV 或 Aggref 表达式复制到父计划级别之后对它们进行递归处理
 * （这实际上是在 build_subplan 中处理）。
 */
Node * SS_replace_correlation_vars(PlannerInfo *fc_root, Node *fc_expr)
{
	/* 不需要设置树遍历，因此我们出发吧 */
	return fc_replace_correlation_vars_mutator(fc_expr, fc_root);
}

static Node * fc_replace_correlation_vars_mutator(Node *fc_node, PlannerInfo *fc_root)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		if (((Var *) fc_node)->varlevelsup > 0)
			return (Node *) replace_outer_var(fc_root, (Var *) fc_node);
	}
	if (IsA(fc_node, PlaceHolderVar))
	{
		if (((PlaceHolderVar *) fc_node)->phlevelsup > 0)
			return (Node *) replace_outer_placeholdervar(fc_root,
														 (PlaceHolderVar *) fc_node);
	}
	if (IsA(fc_node, Aggref))
	{
		if (((Aggref *) fc_node)->agglevelsup > 0)
			return (Node *) replace_outer_agg(fc_root, (Aggref *) fc_node);
	}
	if (IsA(fc_node, GroupingFunc))
	{
		if (((GroupingFunc *) fc_node)->agglevelsup > 0)
			return (Node *) replace_outer_grouping(fc_root, (GroupingFunc *) fc_node);
	}
	return expression_tree_mutator(fc_node,
								   fc_replace_correlation_vars_mutator,
								   (void *) fc_root);
}

/*
 * 在给定的表达式中扩展子链接为子计划。
 *
 * isQual 参数指示这个表达式是否是 WHERE/HAVING 限定表达式。如果是，则任何在顶层出现的子链接不需要区分 FALSE 和 UNKNOWN 返回值。
 */
Node * SS_process_sublinks(PlannerInfo *fc_root, Node *fc_expr, bool fc_isQual)
{
	process_sublinks_context fc_context;

	fc_context.root = fc_root;
	fc_context.isTopQual = fc_isQual;
	return fc_process_sublinks_mutator(fc_expr, &fc_context);
}

static Node * fc_process_sublinks_mutator(Node *fc_node, process_sublinks_context *fc_context)
{
	process_sublinks_context fc_locContext;

	fc_locContext.root = fc_context->root;

	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, SubLink))
	{
		SubLink    *fc_sublink = (SubLink *) fc_node;
		Node	   *fc_testexpr;

		/*
		 * 首先，如果有的话，递归处理左侧表达式。
		 * 它们不再是顶层了。
		 */
		fc_locContext.isTopQual = false;
		fc_testexpr = fc_process_sublinks_mutator(fc_sublink->testexpr, &fc_locContext);

		/*
		 * 现在构建子计划节点并生成要返回的表达式。
		 */
		return fc_make_subplan(fc_context->root,
							(Query *) fc_sublink->subselect,
							fc_sublink->subLinkType,
							fc_sublink->subLinkId,
							fc_testexpr,
							fc_context->isTopQual);
	}

	/*
	 * 在这里不要递归到外层 PHV、Aggref 或 GroupingFunc 的参数中。参数中的任何子链接必须在外层查询级别处理；它们将在 build_subplan 
	 * 收集 PHV、Aggref 或 GroupingFunc 作为要传递给当前子计划的参数时处理。
	 */
	if (IsA(fc_node, PlaceHolderVar))
	{
		if (((PlaceHolderVar *) fc_node)->phlevelsup > 0)
			return fc_node;
	}
	else if (IsA(fc_node, Aggref))
	{
		if (((Aggref *) fc_node)->agglevelsup > 0)
			return fc_node;
	}
	else if (IsA(fc_node, GroupingFunc))
	{
		if (((GroupingFunc *) fc_node)->agglevelsup > 0)
			return fc_node;
	}

	/*
	 * 我们在输入中不应该看到子计划表达式（因为这是创建它们的常规代码）。我们也不应该直接在查询上被调用。
	 */
	Assert(!IsA(fc_node, SubPlan));
	Assert(!IsA(fc_node, AlternativeSubPlan));
	Assert(!IsA(fc_node, Query));

	/*
	 * 由于 make_subplan() 可能返回 AND 或 OR 子句，我们必须采取措施以保留限定符的 AND/OR 平坦性。我们假设输入已经被 AND/OR 扁平化，因此这里不需要递归。
	 *
	 * （由于这里的编码，我们不会在 AND 的列表子节点上被调用；输入 *尚未* 处于隐式 AND 格式。因此不需要检查裸列表。）
	 *
	 * 在顶层 AND/OR 子句结构内的任何地方，我们可以告诉 make_subplan() NULL 和 FALSE 是可以互换的。因此 isTopQual 在两种情况下都向下传播。（请注意，这与 Postgres 中大多数其他地方使用的“顶层限定符”的含义不同。）
	 */
	if (is_andclause(fc_node))
	{
		List	   *fc_newargs = NIL;
		ListCell   *fc_l;

		/* 仍在限定符顶层 */
		fc_locContext.isTopQual = fc_context->isTopQual;

		foreach(fc_l, ((BoolExpr *) fc_node)->args)
		{
			Node	   *fc_newarg;

			fc_newarg = fc_process_sublinks_mutator(lfirst(fc_l), &fc_locContext);
			if (is_andclause(fc_newarg))
				fc_newargs = list_concat(fc_newargs, ((BoolExpr *) fc_newarg)->args);
			else
				fc_newargs = lappend(fc_newargs, fc_newarg);
		}
		return (Node *) make_andclause(fc_newargs);
	}

	if (is_orclause(fc_node))
	{
		List	   *fc_newargs = NIL;
		ListCell   *fc_l;

		/* 仍在限定符顶层 */
		fc_locContext.isTopQual = fc_context->isTopQual;

		foreach(fc_l, ((BoolExpr *) fc_node)->args)
		{
			Node	   *fc_newarg;

			fc_newarg = fc_process_sublinks_mutator(lfirst(fc_l), &fc_locContext);
			if (is_orclause(fc_newarg))
				fc_newargs = list_concat(fc_newargs, ((BoolExpr *) fc_newarg)->args);
			else
				fc_newargs = lappend(fc_newargs, fc_newarg);
		}
		return (Node *) make_orclause(fc_newargs);
	}

	/*
	 * 如果我们通过任何其他节点递归而不是 AND 或 OR 节点，我们肯定不再处于顶级限定符级别。
	 */
	fc_locContext.isTopQual = false;

	return expression_tree_mutator(fc_node,
								   fc_process_sublinks_mutator,
								   (void *) &fc_locContext);
}

/*
 * SS_identify_outer_params - 识别来自外层级别的可用参数
 *
 * 这必须在给定查询级别及其所有子级（这意味着在处理查询级别结束时执行最为实际）中完成 SS_replace_correlation_vars 和 SS_process_sublinks 
 * 处理后运行。我们计算外层级别将向此级别+子级提供的 paramIds 集，并将其记录在 root->outer_params 中，以便在最终计划清理中计算 extParam/allParam 集时使用。
 * （我们不能在那时进行计算，因为上层的 plan_params 列表是短暂的，到时将被删除。）
 */
void SS_identify_outer_params(PlannerInfo *fc_root)
{
	Bitmapset  *fc_outer_params;
	PlannerInfo *fc_proot;
	ListCell   *fc_l;

	/*
	 * 如果树中任何地方都没有分配参数，我们当然不需要在这里做任何事情。
	 */
	if (fc_root->glob->paramExecTypes == NIL)
		return;

	/*
	 * 扫描此级别之上的所有查询级别，以查看哪些参数应从它们提供，要么是因为下层查询级别请求了它们（通过 plan_params），要么是因为它们将从那些级别的 initPlans 中获得。
	 */
	fc_outer_params = NULL;
	for (fc_proot = fc_root->parent_root; fc_proot != NULL; fc_proot = fc_proot->parent_root)
	{
		/* 包含普通的 Var/PHV/Aggref/GroupingFunc 参数 */
		foreach(fc_l, fc_proot->plan_params)
		{
			PlannerParamItem *fc_pitem = (PlannerParamItem *) lfirst(fc_l);

			fc_outer_params = bms_add_member(fc_outer_params, fc_pitem->paramId);
		}
		/* 包含外层级别 initPlans 的任何输出 */
		foreach(fc_l, fc_proot->init_plans)
		{
			SubPlan    *fc_initsubplan = (SubPlan *) lfirst(fc_l);
			ListCell   *fc_l2;

			foreach(fc_l2, fc_initsubplan->setParam)
			{
				fc_outer_params = bms_add_member(fc_outer_params, lfirst_int(fc_l2));
			}
		}
		/* 如果正在规划递归查询，则包含工作表 ID */
		if (fc_proot->wt_param_id >= 0)
			fc_outer_params = bms_add_member(fc_outer_params, fc_proot->wt_param_id);
	}
	fc_root->outer_params = fc_outer_params;
}



/*
 * SS_charge_for_initplans - 计算 initplans 在路径成本和并行性中的作用
 *
 * 如果在当前查询级别创建了任何 initPlans，它们将被附加到
 * 从给定关系中选择的路径创建的计划树中。增加该关系所有路径的成本
 * 以考虑它们，并确保这些路径被标记为不安全并行，因为我们目前无法
 * 将 initPlans 传递给并行工作者。
 *
 * 这与 SS_attach_initplans 是分开的，因为我们可能会在 create_plan() 中
 * 根据选择的路径有条件地创建更多的 initPlans。然而，预计生成这些 initPlans
 * 的路径已经考虑了它们的成本和并行安全性影响。
 */
void SS_charge_for_initplans(PlannerInfo *fc_root, RelOptInfo *fc_final_rel)
{
	Cost		fc_initplan_cost;
	ListCell   *fc_lc;

	/* 如果没有 initPlans 则无需进行任何操作 */
	if (fc_root->init_plans == NIL)
		return;

	/*
	 * 只需计算一次成本增量，因为它对所有路径都是相同的。
	 * 我们假设每个 initPlan 在顶层计划启动期间运行一次。
	 * 这是一个保守的高估，因为实际上一个 initPlan 可能在计划启动后
	 * 执行，或甚至根本不执行。
	 */
	fc_initplan_cost = 0;
	foreach(fc_lc, fc_root->init_plans)
	{
		SubPlan    *fc_initsubplan = (SubPlan *) lfirst(fc_lc);

		fc_initplan_cost += fc_initsubplan->startup_cost + fc_initsubplan->per_call_cost;
	}

	/*
	 * 现在调整成本和并行安全标志。
	 */
	foreach(fc_lc, fc_final_rel->pathlist)
	{
		Path	   *fc_path = (Path *) lfirst(fc_lc);

		fc_path->startup_cost += fc_initplan_cost;
		fc_path->total_cost += fc_initplan_cost;
		fc_path->parallel_safe = false;
	}

	/*
	 * 忘掉任何部分路径并清除 consider_parallel；如果我们附加了
	 * initPlan，它们就无法使用。
	 */
	fc_final_rel->partial_pathlist = NIL;
	fc_final_rel->consider_parallel = false;

	/* 我们在这里不需要调用 set_cheapest()，调用者会处理它 */
}

/*
 * SS_attach_initplans - 将 initplans 附加到最上层的计划节点
 *
 * 将在当前查询级别创建的任何 initplans 附加到指定的计划节点，
 * 该节点通常应该是查询级别的最上层节点。
 * （原则上，initPlans可以放在任何节点上或以上的引用位置；但是
 * 没有理由将它们放在低于最上层节点的位置，因此我们不麻烦去追踪
 * 它们的确切来源。）
 *
 * 我们不修改计划节点的成本或并行安全标志。initplans 必须在
 * SS_charge_for_initplans 中，或通过任何后续代码通过
 * SS_make_initplan_from_plan 添加。
 */
void SS_attach_initplans(PlannerInfo *fc_root, Plan *fc_plan)
{
	fc_plan->initPlan = fc_root->init_plans;
}

/*
 * SS_finalize_plan - 对完成的计划进行最终参数处理。
 *
 * 这递归计算给定计划树中所有计划节点的 extParam 和 allParam 集合。
 * （哦，还有 RangeTblFunction.funcparams。）
 *
 * 我们假设 SS_finalize_plan 已经在任何 initplans 或子计划上运行
 * 计划树可以引用的。
 */
void SS_finalize_plan(PlannerInfo *fc_root, Plan *fc_plan)
{
	/* 无需设置，仅需递归遍历计划树。 */
	(void) fc_finalize_plan(fc_root, fc_plan, -1, fc_root->outer_params, NULL);
}

/*
 * 计划树中所有节点的递归处理
 *
 * gather_param 是一个祖先 Gather/GatherMerge 的 rescan_param，
 * 如果没有则为 -1。
 *
 * valid_params 是由外部计划级别提供的参数 ID 集合，
 * 这些 ID 在该计划节点或其子节点中是有效的引用。
 *
 * scan_params 是一组参数 ID，用于强制扫描计划节点进行引用。
 * 这是为了支持 EvalPlanQual，并且在递归的顶部始终为 NULL。
 *
 * 返回值是给定计划节点计算出的 allParam 集合。
 * 这只是一个内部记号上的便利：我们可以将子计划的 allParams 
 * 添加到该级别中感兴趣的参数 ID 集合，在递归到该子级别的同一语句中。
 *
 * 请不要对调用者的 valid_params 或 scan_params 值进行任意修改！
 *
 * 注意：尽管我们尝试处理树中的任何 initPlans，但逻辑并不完全正确。
 * 问题在于一个计划节点可能将其 initPlan 的输出 Param 作为目标列表项返回，
 * 在这种情况下，父计划级别引用同一参数是有效的；
 * 父级的使用将通过 setrefs.c 转换为引用子计划节点的 Var。
 * 但这个函数会将父级的引用视为超出范围并对此进行抱怨。
 * 目前，这并不重要，因为计划者只在查询级别的最上层计划节点附件 initPlans，
 * 因此这种情况不会出现。如果我们将此处理合并到 setrefs.c 中，也许它
 * 可以更干净地处理。
 */
static Bitmapset * fc_finalize_plan(PlannerInfo *fc_root, Plan *fc_plan,
			  int fc_gather_param,
			  Bitmapset *fc_valid_params,
			  Bitmapset *fc_scan_params)
{
	finalize_primnode_context fc_context;
	int			fc_locally_added_param;
	Bitmapset  *fc_nestloop_params;
	Bitmapset  *fc_initExtParam;
	Bitmapset  *fc_initSetParam;
	Bitmapset  *fc_child_params;
	ListCell   *fc_l;

	if (fc_plan == NULL)
		return NULL;

	fc_context.root = fc_root;
	fc_context.paramids = NULL;	/* 初始化集合为空 */
	fc_locally_added_param = -1;	/* 并不存在 */
	fc_nestloop_params = NULL;		/* 也没有 */

	/*
	 * 检查任何 initPlans 以确定它们引用的外部参数集合
	 * 和它们提供的输出参数集合。 （我们假设
	 * SS_finalize_plan 已在它们上运行。）
	 */
	fc_initExtParam = fc_initSetParam = NULL;
	foreach(fc_l, fc_plan->initPlan)
	{
		SubPlan    *fc_initsubplan = (SubPlan *) lfirst(fc_l);
		Plan	   *fc_initplan = planner_subplan_get_plan(fc_root, fc_initsubplan);
		ListCell   *fc_l2;

		fc_initExtParam = bms_add_members(fc_initExtParam, fc_initplan->extParam);
		foreach(fc_l2, fc_initsubplan->setParam)
		{
			fc_initSetParam = bms_add_member(fc_initSetParam, lfirst_int(fc_l2));
		}
	}

	/* 任何 setParams 在此节点及其子节点中都是有效可引用的 */
	if (fc_initSetParam)
		fc_valid_params = bms_union(fc_valid_params, fc_initSetParam);

	/*
	 * 当我们调用 finalize_primnode 时，context.paramids 集合会自动
	 * 合并。但在递归到自身时，我们必须通过艰苦的方式来实现。我们希望
	 * paramids 集合包括子计划中的参数以及当前层级的参数。
	 */

	/* 在 targetlist 和 qual 中查找参数 */
	fc_finalize_primnode((Node *) fc_plan->targetlist, &fc_context);
	fc_finalize_primnode((Node *) fc_plan->qual, &fc_context);

	/*
	 * 如果这是一个支持并行的扫描节点，则标记为依赖于父级
	 * Gather/GatherMerge 的 rescan Param。
	 */
	if (fc_plan->parallel_aware)
	{
		if (fc_gather_param < 0)
			elog(ERROR, "parallel-aware plan node is not below a Gather");
		fc_context.paramids =
			bms_add_member(fc_context.paramids, fc_gather_param);
	}

	/* 检查附加的节点类型特定字段 */
	switch (nodeTag(fc_plan))
	{
		case T_Result:
			fc_finalize_primnode(((Result *) fc_plan)->resconstantqual,
							  &fc_context);
			break;

		case T_SeqScan:
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_SampleScan:
			fc_finalize_primnode((Node *) ((SampleScan *) fc_plan)->tablesample,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_IndexScan:
			fc_finalize_primnode((Node *) ((IndexScan *) fc_plan)->indexqual,
							  &fc_context);
			fc_finalize_primnode((Node *) ((IndexScan *) fc_plan)->indexorderby,
							  &fc_context);

			/*
			 * 我们不需要查看 indexqualorig，因为它将与 indexqual
			 * 具有相同的参数引用。同样，我们可以忽略
			 * indexorderbyorig。
			 */
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_IndexOnlyScan:
			fc_finalize_primnode((Node *) ((IndexOnlyScan *) fc_plan)->indexqual,
							  &fc_context);
			fc_finalize_primnode((Node *) ((IndexOnlyScan *) fc_plan)->recheckqual,
							  &fc_context);
			fc_finalize_primnode((Node *) ((IndexOnlyScan *) fc_plan)->indexorderby,
							  &fc_context);

			/*
			 * 我们不需要查看 indextlist，因为它无法包含 Params。
			 */
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_BitmapIndexScan:
			fc_finalize_primnode((Node *) ((BitmapIndexScan *) fc_plan)->indexqual,
							  &fc_context);

			/*
			 * 我们不需要查看 indexqualorig，因为它将与 indexqual
			 * 具有相同的参数引用。
			 */
			break;

		case T_BitmapHeapScan:
			fc_finalize_primnode((Node *) ((BitmapHeapScan *) fc_plan)->bitmapqualorig,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_TidScan:
			fc_finalize_primnode((Node *) ((TidScan *) fc_plan)->tidquals,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_TidRangeScan:
			fc_finalize_primnode((Node *) ((TidRangeScan *) fc_plan)->tidrangequals,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_SubqueryScan:
			{
				SubqueryScan *fc_sscan = (SubqueryScan *) fc_plan;
				RelOptInfo *fc_rel;
				Bitmapset  *fc_subquery_params;

				/* 我们必须对子查询运行 finalize_plan */
				fc_rel = find_base_rel(fc_root, fc_sscan->scan.scanrelid);
				fc_subquery_params = fc_rel->subroot->outer_params;
				if (fc_gather_param >= 0)
					fc_subquery_params = bms_add_member(bms_copy(fc_subquery_params),
													 fc_gather_param);
				fc_finalize_plan(fc_rel->subroot, fc_sscan->subplan, fc_gather_param,
							  fc_subquery_params, NULL);

				/* 现在我们可以将其 extParams 添加到父级的参数中 */
				fc_context.paramids = bms_add_members(fc_context.paramids,
												   fc_sscan->subplan->extParam);
				/* 我们也需要 scan_params */
				fc_context.paramids = bms_add_members(fc_context.paramids,
												   fc_scan_params);
			}
			break;

		case T_FunctionScan:
			{
				FunctionScan *fc_fscan = (FunctionScan *) fc_plan;
				ListCell   *fc_lc;

				/*
				 * 在每个函数
				 * 表达式上独立调用 finalize_primnode，以便我们可以记录
				 * 每个中引用的参数，从而决定在重新扫描时需要
				 * 重新评估哪些。
				 */
				foreach(fc_lc, fc_fscan->functions)
				{
					RangeTblFunction *fc_rtfunc = (RangeTblFunction *) lfirst(fc_lc);
					finalize_primnode_context fc_funccontext;

					fc_funccontext = fc_context;
					fc_funccontext.paramids = NULL;

					fc_finalize_primnode(fc_rtfunc->funcexpr, &fc_funccontext);

					/* 记住执行结果 */
					fc_rtfunc->funcparams = fc_funccontext.paramids;

					/* 将函数的参数添加到整体集合中 */
					fc_context.paramids = bms_add_members(fc_context.paramids,
													   fc_funccontext.paramids);
				}

				fc_context.paramids = bms_add_members(fc_context.paramids,
												   fc_scan_params);
			}
			break;

		case T_TableFuncScan:
			fc_finalize_primnode((Node *) ((TableFuncScan *) fc_plan)->tablefunc,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_ValuesScan:
			fc_finalize_primnode((Node *) ((ValuesScan *) fc_plan)->values_lists,
							  &fc_context);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_CteScan:
			{
				/*
				 * 你可能会认为我们应该将节点的 cteParam 添加到
				 * paramids 中，但我们不应该这样，因为该参数只是一个
				 * 链接机制，用于多个 CteScan 节点对应同一
				 * CTE；它从未用于更改参数的信号。我们需要做的是查找
				 * 引用的 CTE 计划并合并其外部 paramids，以便在 CTE
				 * 引用外层变量时能够发生正确的事情。请参见 bug #4902 的测试用例。
				 * （我们假设 SS_finalize_plan 已经在 CTE 计划上运行过。）
				 */
				int			fc_plan_id = ((CteScan *) fc_plan)->ctePlanId;
				Plan	   *fc_cteplan;

				/* 所以，做这个... */
				if (fc_plan_id < 1 || fc_plan_id > list_length(fc_root->glob->subplans))
					elog(ERROR, "could not find plan for CteScan referencing plan ID %d",
						 fc_plan_id);
				fc_cteplan = (Plan *) list_nth(fc_root->glob->subplans, fc_plan_id - 1);
				fc_context.paramids =
					bms_add_members(fc_context.paramids, fc_cteplan->extParam);

#ifdef NOT_USED
				/* ...但不是这个 */
				fc_context.paramids =
					bms_add_member(fc_context.paramids,
								   ((CteScan *) fc_plan)->cteParam);
#endif

				fc_context.paramids = bms_add_members(fc_context.paramids,
												   fc_scan_params);
			}
			break;

		case T_WorkTableScan:
			fc_context.paramids =
				bms_add_member(fc_context.paramids,
							   ((WorkTableScan *) fc_plan)->wtParam);
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_NamedTuplestoreScan:
			fc_context.paramids = bms_add_members(fc_context.paramids, fc_scan_params);
			break;

		case T_ForeignScan:
			{
				ForeignScan *fc_fscan = (ForeignScan *) fc_plan;

				fc_finalize_primnode((Node *) fc_fscan->fdw_exprs,
								  &fc_context);
				fc_finalize_primnode((Node *) fc_fscan->fdw_recheck_quals,
								  &fc_context);

				/* 我们假设 fdw_scan_tlist 不能包含 Params */
				fc_context.paramids = bms_add_members(fc_context.paramids,
												   fc_scan_params);
			}
			break;

		case T_CustomScan:
			{
				CustomScan *fc_cscan = (CustomScan *) fc_plan;
				ListCell   *fc_lc;

				fc_finalize_primnode((Node *) fc_cscan->custom_exprs,
								  &fc_context);
				/* 我们假设 custom_scan_tlist 不能包含 Params */
				fc_context.paramids =
					bms_add_members(fc_context.paramids, fc_scan_params);

				/* 子节点（如果有） */
				foreach(fc_lc, fc_cscan->custom_plans)
				{
					fc_context.paramids =
						bms_add_members(fc_context.paramids,
										fc_finalize_plan(fc_root,
													  (Plan *) lfirst(fc_lc),
													  fc_gather_param,
													  fc_valid_params,
													  fc_scan_params));
				}
			}
			break;

		case T_ModifyTable:
			{
				ModifyTable *fc_mtplan = (ModifyTable *) fc_plan;

				/* 强制后代扫描节点引用 epqParam */
				fc_locally_added_param = fc_mtplan->epqParam;
				fc_valid_params = bms_add_member(bms_copy(fc_valid_params),
											  fc_locally_added_param);
				fc_scan_params = bms_add_member(bms_copy(fc_scan_params),
											 fc_locally_added_param);
				fc_finalize_primnode((Node *) fc_mtplan->returningLists,
								  &fc_context);
				fc_finalize_primnode((Node *) fc_mtplan->onConflictSet,
								  &fc_context);
				fc_finalize_primnode((Node *) fc_mtplan->onConflictWhere,
								  &fc_context);
				/* exclRelTlist 仅包含 Vars，不需要检查 */
			}
			break;

		case T_Append:
			{
				ListCell   *fc_l;

				foreach(fc_l, ((Append *) fc_plan)->appendplans)
				{
					fc_context.paramids =
						bms_add_members(fc_context.paramids,
										fc_finalize_plan(fc_root,
													  (Plan *) lfirst(fc_l),
													  fc_gather_param,
													  fc_valid_params,
													  fc_scan_params));
				}
			}
			break;

		case T_MergeAppend:
			{
				ListCell   *fc_l;

				foreach(fc_l, ((MergeAppend *) fc_plan)->mergeplans)
				{
					fc_context.paramids =
						bms_add_members(fc_context.paramids,
										fc_finalize_plan(fc_root,
													  (Plan *) lfirst(fc_l),
													  fc_gather_param,
													  fc_valid_params,
													  fc_scan_params));
				}
			}
			break;

		case T_BitmapAnd:
			{
				ListCell   *fc_l;

				foreach(fc_l, ((BitmapAnd *) fc_plan)->bitmapplans)
				{
					fc_context.paramids =
						bms_add_members(fc_context.paramids,
										fc_finalize_plan(fc_root,
													  (Plan *) lfirst(fc_l),
													  fc_gather_param,
													  fc_valid_params,
													  fc_scan_params));
				}
			}
			break;

		case T_BitmapOr:
			{
				ListCell   *fc_l;

				foreach(fc_l, ((BitmapOr *) fc_plan)->bitmapplans)
				{
					fc_context.paramids =
						bms_add_members(fc_context.paramids,
										fc_finalize_plan(fc_root,
													  (Plan *) lfirst(fc_l),
													  fc_gather_param,
													  fc_valid_params,
													  fc_scan_params));
				}
			}
			break;

		case T_NestLoop:
			{
				ListCell   *fc_l;

				fc_finalize_primnode((Node *) ((Join *) fc_plan)->joinqual,
								  &fc_context);
				/* 收集将传递给右子节点的参数集合 */
				foreach(fc_l, ((NestLoop *) fc_plan)->nestParams)
				{
					NestLoopParam *fc_nlp = (NestLoopParam *) lfirst(fc_l);

					fc_nestloop_params = bms_add_member(fc_nestloop_params,
													 fc_nlp->paramno);
				}
			}
			break;

		case T_MergeJoin:
			fc_finalize_primnode((Node *) ((Join *) fc_plan)->joinqual,
							  &fc_context);
			fc_finalize_primnode((Node *) ((MergeJoin *) fc_plan)->mergeclauses,
							  &fc_context);
			break;

		case T_HashJoin:
			fc_finalize_primnode((Node *) ((Join *) fc_plan)->joinqual,
							  &fc_context);
			fc_finalize_primnode((Node *) ((HashJoin *) fc_plan)->hashclauses,
							  &fc_context);
			break;

		case T_Hash:
			fc_finalize_primnode((Node *) ((Hash *) fc_plan)->hashkeys,
							  &fc_context);
			break;

		case T_Limit:
			fc_finalize_primnode(((Limit *) fc_plan)->limitOffset,
							  &fc_context);
			fc_finalize_primnode(((Limit *) fc_plan)->limitCount,
							  &fc_context);
			break;

		case T_RecursiveUnion:
			/* 子节点可以引用 wtParam */
			fc_locally_added_param = ((RecursiveUnion *) fc_plan)->wtParam;
			fc_valid_params = bms_add_member(bms_copy(fc_valid_params),
										  fc_locally_added_param);
			/* wtParam 不会被添加到 scan_params */
			break;

		case T_LockRows:
			/* 强制后代扫描节点引用 epqParam */
			fc_locally_added_param = ((LockRows *) fc_plan)->epqParam;
			fc_valid_params = bms_add_member(bms_copy(fc_valid_params),
										  fc_locally_added_param);
			fc_scan_params = bms_add_member(bms_copy(fc_scan_params),
										 fc_locally_added_param);
			break;

		case T_Agg:
			{
				Agg		   *fc_agg = (Agg *) fc_plan;

				/*
				 * AGG_HASHED 计划需要知道在聚合调用中引用了哪些 Params。
				 * 进行单独扫描以识别它们。
				 */
				if (fc_agg->aggstrategy == AGG_HASHED)
				{
					finalize_primnode_context fc_aggcontext;

					fc_aggcontext.root = fc_root;
					fc_aggcontext.paramids = NULL;
					fc_finalize_agg_primnode((Node *) fc_agg->plan.targetlist,
										  &fc_aggcontext);
					fc_finalize_agg_primnode((Node *) fc_agg->plan.qual,
										  &fc_aggcontext);
					fc_agg->aggParams = fc_aggcontext.paramids;
				}
			}
			break;

		case T_WindowAgg:
			fc_finalize_primnode(((WindowAgg *) fc_plan)->startOffset,
							  &fc_context);
			fc_finalize_primnode(((WindowAgg *) fc_plan)->endOffset,
							  &fc_context);
			break;

		case T_Gather:
			/* 子节点可以引用 rescan_param（如果有） */
			fc_locally_added_param = ((Gather *) fc_plan)->rescan_param;
			if (fc_locally_added_param >= 0)
			{
				fc_valid_params = bms_add_member(bms_copy(fc_valid_params),
											  fc_locally_added_param);

				/*
				 * 我们目前不支持嵌套的 Gather。就此函数而言，问题在于
				 * 如何识别哪些子节点依赖于哪个 Gather。
				 */
				Assert(fc_gather_param < 0);
				/* 将 rescan_param 传递给子并行意识节点 */
				fc_gather_param = fc_locally_added_param;
			}
			/* rescan_param 不会被添加到 scan_params */
			break;

		case T_GatherMerge:
			/* 子节点可以引用 rescan_param（如果有） */
			fc_locally_added_param = ((GatherMerge *) fc_plan)->rescan_param;
			if (fc_locally_added_param >= 0)
			{
				fc_valid_params = bms_add_member(bms_copy(fc_valid_params),
											  fc_locally_added_param);

				/*
				 * 我们目前不支持嵌套的 Gather。就此函数而言，问题在于
				 * 如何识别哪些子节点依赖于哪个 Gather。
				 */
				Assert(fc_gather_param < 0);
				/* 将 rescan_param 传递给子并行意识节点 */
				fc_gather_param = fc_locally_added_param;
			}
			/* rescan_param 不会被添加到 scan_params */
			break;

		case T_Memoize:
			fc_finalize_primnode((Node *) ((Memoize *) fc_plan)->param_exprs,
							  &fc_context);
			break;

		case T_ProjectSet:
		case T_Material:
		case T_Sort:
		case T_IncrementalSort:
		case T_Unique:
		case T_SetOp:
		case T_Group:
			/* 不需要修复任何节点类型特定字段 */
			break;

		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_plan));
	}

	/* 处理左子树和右子树计划，如果有的话 */
	fc_child_params = fc_finalize_plan(fc_root,
								 fc_plan->lefttree,
								 fc_gather_param,
								 fc_valid_params,
								 fc_scan_params);
	fc_context.paramids = bms_add_members(fc_context.paramids, fc_child_params);

	if (fc_nestloop_params)
	{
		/* 右子树可以引用 nestloop_params 以及 valid_params */
		fc_child_params = fc_finalize_plan(fc_root,
									 fc_plan->righttree,
									 fc_gather_param,
									 bms_union(fc_nestloop_params, fc_valid_params),
									 fc_scan_params);
		/* ... 它们在我这个级别上不算作使用的参数 */
		fc_child_params = bms_difference(fc_child_params, fc_nestloop_params);
		bms_free(fc_nestloop_params);
	}
	else
	{
		/* 简单的情况 */
		fc_child_params = fc_finalize_plan(fc_root,
									 fc_plan->righttree,
									 fc_gather_param,
									 fc_valid_params,
									 fc_scan_params);
	}
	fc_context.paramids = bms_add_members(fc_context.paramids, fc_child_params);

	/*
	 * 任何本地生成的参数不算作其生成的计划节点的外部依赖。
	 * （注意：如果我们更改了 valid_params 和/或 scan_params，
	 * 我们会泄漏那些 bitmapsets；清理它们的工作不值得。）
	 */
	if (fc_locally_added_param >= 0)
	{
		fc_context.paramids = bms_del_member(fc_context.paramids,
										  fc_locally_added_param);
	}

	/* 现在我们拥有此节点和子节点中引用的所有 paramids */

	if (!bms_is_subset(fc_context.paramids, fc_valid_params))
		elog(ERROR, "plan should not reference subplan's variable");

	/*
	 * 计划节点的 allParam 和 extParam 字段应该包括它所有的
	 * 引用 paramids，以及任何子初始化计划的贡献。
	 * 然而，初始化计划的任何 setParams 不应出现在
	 * 父节点的 extParams 中，只能出现在其 allParams 中。
	 * （某些初始化计划可能有 extParams，它们是其它初始化计划的 setParams。）
	 */

	/* allParam 必须包括初始化计划的 extParams 和 setParams */
	fc_plan->allParam = bms_union(fc_context.paramids, fc_initExtParam);
	fc_plan->allParam = bms_add_members(fc_plan->allParam, fc_initSetParam);
	/* extParam 必须包括任何初始化计划 extParams */
	fc_plan->extParam = bms_union(fc_context.paramids, fc_initExtParam);
	/* 但不包括任何初始化计划 setParams */
	fc_plan->extParam = bms_del_members(fc_plan->extParam, fc_initSetParam);

	/*
	 * 为了在执行时提高速度，确保 extParam/allParam 如果是空集合
	 * 则实际上为 NULL。
	 */
	if (bms_is_empty(fc_plan->extParam))
		fc_plan->extParam = NULL;
	if (bms_is_empty(fc_plan->allParam))
		fc_plan->allParam = NULL;

	return fc_plan->allParam;
}

/*
 * finalize_primnode：将所有在给定表达式树中出现（或将出现）的
 * PARAM_EXEC 参数的 ID 添加到结果集中。
 */
static bool fc_finalize_primnode(Node *fc_node, finalize_primnode_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Param))
	{
		if (((Param *) fc_node)->paramkind == PARAM_EXEC)
		{
			int			fc_paramid = ((Param *) fc_node)->paramid;

			fc_context->paramids = bms_add_member(fc_context->paramids, fc_paramid);
		}
		return false;			/* 这里没有更多的事情要做 */
	}
	else if (IsA(fc_node, Aggref))
	{
		/*
		 * 检查聚合是否会在 setrefs.c 期间被替换为引用子查询输出的参数。
		 * 如果是这样，我们必须在这里考虑该参数。
		 * （出于各种原因，在 setrefs.c 之前进行该替换不方便，
		 * 后续处理也不方便。因此我们需要在这里写些临时处理。）
		 */
		Aggref	   *fc_aggref = (Aggref *) fc_node;
		Param	   *fc_aggparam;

		fc_aggparam = find_minmax_agg_replacement_param(fc_context->root, fc_aggref);
		if (fc_aggparam != NULL)
			fc_context->paramids = bms_add_member(fc_context->paramids,
											   fc_aggparam->paramid);
		/* 继续检查聚合的参数 */
	}
	else if (IsA(fc_node, SubPlan))
	{
		SubPlan    *fc_subplan = (SubPlan *) fc_node;
		Plan	   *fc_plan = planner_subplan_get_plan(fc_context->root, fc_subplan);
		ListCell   *fc_lc;
		Bitmapset  *fc_subparamids;

		/* 递归进入 testexpr，但不进入 Plan */
		fc_finalize_primnode(fc_subplan->testexpr, fc_context);

		/*
		 * 移除在 testexpr 中引用的子计划输出参数的参数 ID。
		 * 这些对参数更改信号不感兴趣，因为我们总是重新评估子计划。
		 * 请注意，如果计划中的其他地方可能使用相同的参数 ID，这种做法可能效果不好，
		 * 但这种情况不会发生，因为 generate_new_exec_param 从未尝试合并参数。
		 */
		foreach(fc_lc, fc_subplan->paramIds)
		{
			fc_context->paramids = bms_del_member(fc_context->paramids,
											   lfirst_int(fc_lc));
		}

		/* 还检查参数列表 */
		fc_finalize_primnode((Node *) fc_subplan->args, fc_context);

		/*
		 * 将子计划所需的参数添加到 paramids，但排除我们将传递给它的参数。
		 * （我们假设 SS_finalize_plan 已经在子计划上运行过。）
		 */
		fc_subparamids = bms_copy(fc_plan->extParam);
		foreach(fc_lc, fc_subplan->parParam)
		{
			fc_subparamids = bms_del_member(fc_subparamids, lfirst_int(fc_lc));
		}
		fc_context->paramids = bms_join(fc_context->paramids, fc_subparamids);

		return false;			/* 这里没有更多的事情要做 */
	}
	return expression_tree_walker(fc_node, fc_finalize_primnode,
								  (void *) fc_context);
}

/*
 * finalize_agg_primnode：查找给定表达式树中的所有 Aggref 节点，
 * 并将其聚合参数内出现的所有 PARAM_EXEC 参数的 ID 添加到结果集中。
 */
static bool fc_finalize_agg_primnode(Node *fc_node, finalize_primnode_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Aggref))
	{
		Aggref	   *fc_agg = (Aggref *) fc_node;

		/* 我们不应该考虑直接参数（如果有的话） */
		fc_finalize_primnode((Node *) fc_agg->args, fc_context);
		fc_finalize_primnode((Node *) fc_agg->aggfilter, fc_context);
		return false;			/* 此处不能有任何 Aggrefs */
	}
	return expression_tree_walker(fc_node, fc_finalize_agg_primnode,
								  (void *) fc_context);
}

/*
 * SS_make_initplan_output_param - 为初始化计划的输出创建一个参数
 *
 * 这个计划预计会返回给定类型/排序的标量值。
 *
 * 请注意，在某些情况下，初始化计划可能永远不会出现在最终的
 * 计划树中。如果发生这种情况，我们就浪费了一个 PARAM_EXEC 槽，这
 * 也没什么大不了的。
 */
Param * SS_make_initplan_output_param(PlannerInfo *fc_root,
							  Oid fc_resulttype, int32 fc_resulttypmod,
							  Oid fc_resultcollation)
{
	return generate_new_exec_param(fc_root, fc_resulttype,
								   fc_resulttypmod, fc_resultcollation);
}

/*
 * SS_make_initplan_from_plan - 给定一个计划树，使其成为 InitPlan
 *
 * 我们构建一个 EXPR_SUBLINK 子计划节点，并将其放入外部查询级
 * 的初始化计划列表中。一个表示初始化计划输出的参数已通过
 * SS_make_initplan_output_param 分配。
 */
void SS_make_initplan_from_plan(PlannerInfo *fc_root,
						   PlannerInfo *fc_subroot, Plan *fc_plan,
						   Param *fc_prm)
{
	SubPlan    *fc_node;

	/*
	 * 将子计划及其 PlannerInfo 添加到全局列表。
	 */
	fc_root->glob->subplans = lappend(fc_root->glob->subplans, fc_plan);
	fc_root->glob->subroots = lappend(fc_root->glob->subroots, fc_subroot);

	/*
	 * 创建一个子计划节点并将其添加到初始化计划的外部列表中。
	 * 注意它必须出现在任何它可能依赖的其他初始化计划之后
	 * （见 ExecReScan 中的注释）。
	 */
	fc_node = makeNode(SubPlan);
	fc_node->subLinkType = EXPR_SUBLINK;
	fc_node->plan_id = list_length(fc_root->glob->subplans);
	fc_node->plan_name = psprintf("InitPlan %d (returns $%d)",
							   fc_node->plan_id, fc_prm->paramid);
	get_first_col_type(fc_plan, &fc_node->firstColType, &fc_node->firstColTypmod,
					   &fc_node->firstColCollation);
	fc_node->setParam = list_make1_int(fc_prm->paramid);

	fc_root->init_plans = lappend(fc_root->init_plans, fc_node);

	/*
	 * 节点不能有任何输入（因为它是初始化计划），所以 parParam 和 args 列表保持为空。
	 */

	/* 使用计划树中的信息设置子计划的成本 */
	cost_subplan(fc_subroot, fc_node, fc_plan);
}
